@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/dist/plugin.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { sha512, sha256 } from '@noble/hashes/sha2';
|
|
2
|
+
import { blake3 } from '@noble/hashes/blake3';
|
|
3
|
+
import { utf8ToBytes } from '@noble/hashes/utils';
|
|
4
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
5
|
+
import { p256 } from '@noble/curves/nist';
|
|
6
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
7
|
+
import { hexToBytes, bytesToHex } from '@noble/curves/abstract/utils';
|
|
8
|
+
import { fromString, toString } from 'uint8arrays';
|
|
9
|
+
|
|
10
|
+
// src/crypto.ts
|
|
11
|
+
function toBytes(input, encoding = "base64url") {
|
|
12
|
+
if (input === null || input === void 0) {
|
|
13
|
+
return new Uint8Array(0);
|
|
14
|
+
}
|
|
15
|
+
if (input instanceof Uint8Array) {
|
|
16
|
+
return input;
|
|
17
|
+
}
|
|
18
|
+
if (typeof input === "string") {
|
|
19
|
+
switch (encoding) {
|
|
20
|
+
case "base64url":
|
|
21
|
+
return fromString(input, "base64url");
|
|
22
|
+
case "base64":
|
|
23
|
+
return fromString(input, "base64");
|
|
24
|
+
case "hex":
|
|
25
|
+
return hexToBytes(input);
|
|
26
|
+
case "utf8":
|
|
27
|
+
return utf8ToBytes(input);
|
|
28
|
+
default:
|
|
29
|
+
return fromString(input, "base64url");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw new Error("Invalid input type");
|
|
33
|
+
}
|
|
34
|
+
function fromBytes(bytes, encoding = "base64url") {
|
|
35
|
+
switch (encoding) {
|
|
36
|
+
case "base64url":
|
|
37
|
+
return toString(bytes, "base64url");
|
|
38
|
+
case "base64":
|
|
39
|
+
return toString(bytes, "base64");
|
|
40
|
+
case "hex":
|
|
41
|
+
return bytesToHex(bytes);
|
|
42
|
+
case "utf8":
|
|
43
|
+
return toString(bytes, "utf8");
|
|
44
|
+
case "bytes":
|
|
45
|
+
return bytes;
|
|
46
|
+
default:
|
|
47
|
+
return toString(bytes, "base64url");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function digest(data, algorithm = "sha256", inputEncoding = "base64url", outputEncoding = "base64url") {
|
|
51
|
+
const bytes = toBytes(data, inputEncoding);
|
|
52
|
+
let hashBytes;
|
|
53
|
+
switch (algorithm) {
|
|
54
|
+
case "sha256":
|
|
55
|
+
hashBytes = sha256(bytes);
|
|
56
|
+
break;
|
|
57
|
+
case "sha512":
|
|
58
|
+
hashBytes = sha512(bytes);
|
|
59
|
+
break;
|
|
60
|
+
case "blake3":
|
|
61
|
+
hashBytes = blake3(bytes);
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
throw new Error(`Unsupported hash algorithm: ${algorithm}`);
|
|
65
|
+
}
|
|
66
|
+
return fromBytes(hashBytes, outputEncoding);
|
|
67
|
+
}
|
|
68
|
+
function hashMod(data, bits, algorithm = "sha256", inputEncoding = "base64url") {
|
|
69
|
+
if (bits <= 0 || bits > 53) {
|
|
70
|
+
throw new Error("Bits must be between 1 and 53 (JavaScript safe integer limit)");
|
|
71
|
+
}
|
|
72
|
+
const hashBytes = toBytes(digest(data, algorithm, inputEncoding, "base64url"), "base64url");
|
|
73
|
+
const view = new DataView(hashBytes.buffer, hashBytes.byteOffset, Math.min(8, hashBytes.length));
|
|
74
|
+
const fullHash = view.getBigUint64(0, false);
|
|
75
|
+
const modulus = BigInt(2) ** BigInt(bits);
|
|
76
|
+
const result = fullHash % modulus;
|
|
77
|
+
return Number(result);
|
|
78
|
+
}
|
|
79
|
+
function sign(data, privateKey, curve = "secp256k1", inputEncoding = "base64url", keyEncoding = "base64url", outputEncoding = "base64url") {
|
|
80
|
+
const dataBytes = toBytes(data, inputEncoding);
|
|
81
|
+
const keyBytes = toBytes(privateKey, keyEncoding);
|
|
82
|
+
let sigBytes;
|
|
83
|
+
switch (curve) {
|
|
84
|
+
case "secp256k1": {
|
|
85
|
+
const sig = secp256k1.sign(dataBytes, keyBytes, { lowS: true });
|
|
86
|
+
sigBytes = sig.toCompactRawBytes();
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "p256": {
|
|
90
|
+
const sig = p256.sign(dataBytes, keyBytes, { lowS: true });
|
|
91
|
+
sigBytes = sig.toCompactRawBytes();
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "ed25519": {
|
|
95
|
+
sigBytes = ed25519.sign(dataBytes, keyBytes);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
default:
|
|
99
|
+
throw new Error(`Unsupported curve: ${curve}`);
|
|
100
|
+
}
|
|
101
|
+
return fromBytes(sigBytes, outputEncoding);
|
|
102
|
+
}
|
|
103
|
+
function verify(data, signature, publicKey, curve = "secp256k1", inputEncoding = "base64url", sigEncoding = "base64url", keyEncoding = "base64url") {
|
|
104
|
+
try {
|
|
105
|
+
const dataBytes = toBytes(data, inputEncoding);
|
|
106
|
+
const sigBytes = toBytes(signature, sigEncoding);
|
|
107
|
+
const keyBytes = toBytes(publicKey, keyEncoding);
|
|
108
|
+
switch (curve) {
|
|
109
|
+
case "secp256k1": {
|
|
110
|
+
return secp256k1.verify(sigBytes, dataBytes, keyBytes);
|
|
111
|
+
}
|
|
112
|
+
case "p256": {
|
|
113
|
+
return p256.verify(sigBytes, dataBytes, keyBytes);
|
|
114
|
+
}
|
|
115
|
+
case "ed25519": {
|
|
116
|
+
return ed25519.verify(sigBytes, dataBytes, keyBytes);
|
|
117
|
+
}
|
|
118
|
+
default:
|
|
119
|
+
throw new Error(`Unsupported curve: ${curve}`);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function randomBytes(bits = 256, encoding = "base64url") {
|
|
126
|
+
const bytes = Math.ceil(bits / 8);
|
|
127
|
+
const randomBytesArray = new Uint8Array(bytes);
|
|
128
|
+
crypto.getRandomValues(randomBytesArray);
|
|
129
|
+
return fromBytes(randomBytesArray, encoding);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/plugin.ts
|
|
133
|
+
function register(_db, _config = {}) {
|
|
134
|
+
const functions = [
|
|
135
|
+
{
|
|
136
|
+
schema: {
|
|
137
|
+
name: "digest",
|
|
138
|
+
numArgs: -1,
|
|
139
|
+
// Variable arguments: data, algorithm?, inputEncoding?, outputEncoding?
|
|
140
|
+
flags: 1,
|
|
141
|
+
// UTF8
|
|
142
|
+
returnType: { typeClass: "scalar", sqlType: "TEXT" },
|
|
143
|
+
implementation: (...args) => {
|
|
144
|
+
const [data, algorithm = "sha256", inputEncoding = "base64url", outputEncoding = "base64url"] = args;
|
|
145
|
+
return digest(data, algorithm, inputEncoding, outputEncoding);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
schema: {
|
|
151
|
+
name: "sign",
|
|
152
|
+
numArgs: -1,
|
|
153
|
+
// Variable arguments: data, privateKey, curve?, inputEncoding?, keyEncoding?, outputEncoding?
|
|
154
|
+
flags: 1,
|
|
155
|
+
returnType: { typeClass: "scalar", sqlType: "TEXT" },
|
|
156
|
+
implementation: (...args) => {
|
|
157
|
+
const [data, privateKey, curve = "secp256k1", inputEncoding = "base64url", keyEncoding = "base64url", outputEncoding = "base64url"] = args;
|
|
158
|
+
return sign(data, privateKey, curve, inputEncoding, keyEncoding, outputEncoding);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
schema: {
|
|
164
|
+
name: "verify",
|
|
165
|
+
numArgs: -1,
|
|
166
|
+
// Variable arguments: data, signature, publicKey, curve?, inputEncoding?, sigEncoding?, keyEncoding?
|
|
167
|
+
flags: 1,
|
|
168
|
+
returnType: { typeClass: "scalar", sqlType: "INTEGER" },
|
|
169
|
+
implementation: (...args) => {
|
|
170
|
+
const [data, signature, publicKey, curve = "secp256k1", inputEncoding = "base64url", sigEncoding = "base64url", keyEncoding = "base64url"] = args;
|
|
171
|
+
const result = verify(data, signature, publicKey, curve, inputEncoding, sigEncoding, keyEncoding);
|
|
172
|
+
return result ? 1 : 0;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
schema: {
|
|
178
|
+
name: "hash_mod",
|
|
179
|
+
numArgs: -1,
|
|
180
|
+
// Variable arguments: data, bits, algorithm?, inputEncoding?
|
|
181
|
+
flags: 1,
|
|
182
|
+
returnType: { typeClass: "scalar", sqlType: "INTEGER" },
|
|
183
|
+
implementation: (...args) => {
|
|
184
|
+
const [data, bits, algorithm = "sha256", inputEncoding = "base64url"] = args;
|
|
185
|
+
return hashMod(data, bits, algorithm, inputEncoding);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
schema: {
|
|
191
|
+
name: "random_bytes",
|
|
192
|
+
numArgs: -1,
|
|
193
|
+
// Variable arguments: bits?, encoding?
|
|
194
|
+
flags: 1,
|
|
195
|
+
returnType: { typeClass: "scalar", sqlType: "TEXT" },
|
|
196
|
+
implementation: (...args) => {
|
|
197
|
+
const [bits = 256, encoding = "base64url"] = args;
|
|
198
|
+
return randomBytes(bits, encoding);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
];
|
|
203
|
+
return {
|
|
204
|
+
functions,
|
|
205
|
+
vtables: [],
|
|
206
|
+
collations: []
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export { register as default };
|
|
211
|
+
//# sourceMappingURL=plugin.js.map
|
|
212
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/crypto.ts","../src/plugin.ts"],"names":["uint8ArrayFromString","uint8ArrayToString"],"mappings":"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","file":"plugin.js","sourcesContent":["/**\n * Cryptographic Functions for Quereus\n *\n * Idiomatic ES module exports with base64url as default encoding.\n * All functions accept and return base64url strings by default for SQL compatibility.\n */\n\nimport { sha256, sha512 } from '@noble/hashes/sha2';\nimport { blake3 } from '@noble/hashes/blake3';\nimport { concatBytes, utf8ToBytes } from '@noble/hashes/utils';\nimport { secp256k1 } from '@noble/curves/secp256k1';\nimport { p256 } from '@noble/curves/nist';\nimport { ed25519 } from '@noble/curves/ed25519';\nimport { hexToBytes, bytesToHex } from '@noble/curves/abstract/utils';\nimport { toString as uint8ArrayToString, fromString as uint8ArrayFromString } from 'uint8arrays';\n\n// Type definitions\nexport type HashAlgorithm = 'sha256' | 'sha512' | 'blake3';\nexport type CurveType = 'secp256k1' | 'p256' | 'ed25519';\nexport type Encoding = 'base64url' | 'base64' | 'hex' | 'utf8' | 'bytes';\n\n/**\n * Convert input to Uint8Array, handling various encodings\n */\nfunction toBytes(input: string | Uint8Array | null | undefined, encoding: Encoding = 'base64url'): Uint8Array {\n\tif (input === null || input === undefined) {\n\t\treturn new Uint8Array(0);\n\t}\n\n\tif (input instanceof Uint8Array) {\n\t\treturn input;\n\t}\n\n\tif (typeof input === 'string') {\n\t\tswitch (encoding) {\n\t\t\tcase 'base64url':\n\t\t\t\treturn uint8ArrayFromString(input, 'base64url');\n\t\t\tcase 'base64':\n\t\t\t\treturn uint8ArrayFromString(input, 'base64');\n\t\t\tcase 'hex':\n\t\t\t\treturn hexToBytes(input);\n\t\t\tcase 'utf8':\n\t\t\t\treturn utf8ToBytes(input);\n\t\t\tdefault:\n\t\t\t\treturn uint8ArrayFromString(input, 'base64url');\n\t\t}\n\t}\n\n\tthrow new Error('Invalid input type');\n}\n\n/**\n * Convert Uint8Array to string in specified encoding\n */\nfunction fromBytes(bytes: Uint8Array, encoding: Encoding = 'base64url'): string | Uint8Array {\n\tswitch (encoding) {\n\t\tcase 'base64url':\n\t\t\treturn uint8ArrayToString(bytes, 'base64url');\n\t\tcase 'base64':\n\t\t\treturn uint8ArrayToString(bytes, 'base64');\n\t\tcase 'hex':\n\t\t\treturn bytesToHex(bytes);\n\t\tcase 'utf8':\n\t\t\treturn uint8ArrayToString(bytes, 'utf8');\n\t\tcase 'bytes':\n\t\t\treturn bytes;\n\t\tdefault:\n\t\t\treturn uint8ArrayToString(bytes, 'base64url');\n\t}\n}\n\n/**\n * Compute hash digest of input data\n *\n * @param data - Data to hash (base64url string or Uint8Array)\n * @param algorithm - Hash algorithm (default: 'sha256')\n * @param inputEncoding - Encoding of input string (default: 'base64url')\n * @param outputEncoding - Encoding of output (default: 'base64url')\n * @returns Hash digest in specified encoding\n *\n * @example\n * ```typescript\n * // Hash UTF-8 text, output as base64url\n * const hash = digest('hello world', 'sha256', 'utf8');\n *\n * // Hash base64url data with SHA-512\n * const hash2 = digest('SGVsbG8', 'sha512');\n *\n * // Get raw bytes\n * const bytes = digest('data', 'blake3', 'utf8', 'bytes');\n * ```\n */\nexport function digest(\n\tdata: string | Uint8Array,\n\talgorithm: HashAlgorithm = 'sha256',\n\tinputEncoding: Encoding = 'base64url',\n\toutputEncoding: Encoding = 'base64url'\n): string | Uint8Array {\n\tconst bytes = toBytes(data, inputEncoding);\n\n\tlet hashBytes: Uint8Array;\n\tswitch (algorithm) {\n\t\tcase 'sha256':\n\t\t\thashBytes = sha256(bytes);\n\t\t\tbreak;\n\t\tcase 'sha512':\n\t\t\thashBytes = sha512(bytes);\n\t\t\tbreak;\n\t\tcase 'blake3':\n\t\t\thashBytes = blake3(bytes);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow new Error(`Unsupported hash algorithm: ${algorithm}`);\n\t}\n\n\treturn fromBytes(hashBytes, outputEncoding);\n}\n\n/**\n * Hash data and return modulo of specified bit length\n * Useful for generating fixed-size hash values (e.g., 16-bit, 32-bit)\n *\n * @param data - Data to hash\n * @param bits - Number of bits for the result (e.g., 16 for 16-bit hash)\n * @param algorithm - Hash algorithm (default: 'sha256')\n * @param inputEncoding - Encoding of input string (default: 'base64url')\n * @returns Integer hash value modulo 2^bits\n *\n * @example\n * ```typescript\n * // Get 16-bit hash (0-65535)\n * const hash16 = hashMod('hello', 16, 'sha256', 'utf8');\n *\n * // Get 32-bit hash\n * const hash32 = hashMod('world', 32, 'sha256', 'utf8');\n * ```\n */\nexport function hashMod(\n\tdata: string | Uint8Array,\n\tbits: number,\n\talgorithm: HashAlgorithm = 'sha256',\n\tinputEncoding: Encoding = 'base64url'\n): number {\n\tif (bits <= 0 || bits > 53) {\n\t\tthrow new Error('Bits must be between 1 and 53 (JavaScript safe integer limit)');\n\t}\n\n\tconst hashBytes = toBytes(digest(data, algorithm, inputEncoding, 'base64url') as string, 'base64url');\n\n\t// Take first 8 bytes and convert to number\n\tconst view = new DataView(hashBytes.buffer, hashBytes.byteOffset, Math.min(8, hashBytes.length));\n\tconst fullHash = view.getBigUint64(0, false); // big-endian\n\n\t// Modulo by 2^bits\n\tconst modulus = BigInt(2) ** BigInt(bits);\n\tconst result = fullHash % modulus;\n\n\treturn Number(result);\n}\n\n/**\n * Sign data with a private key\n *\n * @param data - Data to sign (typically a hash)\n * @param privateKey - Private key (base64url string or Uint8Array)\n * @param curve - Elliptic curve (default: 'secp256k1')\n * @param inputEncoding - Encoding of data input (default: 'base64url')\n * @param keyEncoding - Encoding of private key (default: 'base64url')\n * @param outputEncoding - Encoding of signature output (default: 'base64url')\n * @returns Signature in specified encoding\n *\n * @example\n * ```typescript\n * // Sign a hash with secp256k1\n * const sig = sign(hashData, privateKey);\n *\n * // Sign with Ed25519\n * const sig2 = sign(hashData, privateKey, 'ed25519');\n * ```\n */\nexport function sign(\n\tdata: string | Uint8Array,\n\tprivateKey: string | Uint8Array,\n\tcurve: CurveType = 'secp256k1',\n\tinputEncoding: Encoding = 'base64url',\n\tkeyEncoding: Encoding = 'base64url',\n\toutputEncoding: Encoding = 'base64url'\n): string | Uint8Array {\n\tconst dataBytes = toBytes(data, inputEncoding);\n\tconst keyBytes = toBytes(privateKey, keyEncoding);\n\n\tlet sigBytes: Uint8Array;\n\n\tswitch (curve) {\n\t\tcase 'secp256k1': {\n\t\t\tconst sig = secp256k1.sign(dataBytes, keyBytes, { lowS: true });\n\t\t\tsigBytes = sig.toCompactRawBytes();\n\t\t\tbreak;\n\t\t}\n\t\tcase 'p256': {\n\t\t\tconst sig = p256.sign(dataBytes, keyBytes, { lowS: true });\n\t\t\tsigBytes = sig.toCompactRawBytes();\n\t\t\tbreak;\n\t\t}\n\t\tcase 'ed25519': {\n\t\t\tsigBytes = ed25519.sign(dataBytes, keyBytes);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tthrow new Error(`Unsupported curve: ${curve}`);\n\t}\n\n\treturn fromBytes(sigBytes, outputEncoding);\n}\n\n/**\n * Verify a signature\n *\n * @param data - Data that was signed\n * @param signature - Signature to verify\n * @param publicKey - Public key\n * @param curve - Elliptic curve (default: 'secp256k1')\n * @param inputEncoding - Encoding of data input (default: 'base64url')\n * @param sigEncoding - Encoding of signature (default: 'base64url')\n * @param keyEncoding - Encoding of public key (default: 'base64url')\n * @returns true if signature is valid, false otherwise\n *\n * @example\n * ```typescript\n * // Verify a signature\n * const isValid = verify(hashData, signature, publicKey);\n *\n * // Verify with Ed25519\n * const isValid2 = verify(hashData, signature, publicKey, 'ed25519');\n * ```\n */\nexport function verify(\n\tdata: string | Uint8Array,\n\tsignature: string | Uint8Array,\n\tpublicKey: string | Uint8Array,\n\tcurve: CurveType = 'secp256k1',\n\tinputEncoding: Encoding = 'base64url',\n\tsigEncoding: Encoding = 'base64url',\n\tkeyEncoding: Encoding = 'base64url'\n): boolean {\n\ttry {\n\t\tconst dataBytes = toBytes(data, inputEncoding);\n\t\tconst sigBytes = toBytes(signature, sigEncoding);\n\t\tconst keyBytes = toBytes(publicKey, keyEncoding);\n\n\t\tswitch (curve) {\n\t\t\tcase 'secp256k1': {\n\t\t\t\treturn secp256k1.verify(sigBytes, dataBytes, keyBytes);\n\t\t\t}\n\t\t\tcase 'p256': {\n\t\t\t\treturn p256.verify(sigBytes, dataBytes, keyBytes);\n\t\t\t}\n\t\t\tcase 'ed25519': {\n\t\t\t\treturn ed25519.verify(sigBytes, dataBytes, keyBytes);\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unsupported curve: ${curve}`);\n\t\t}\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Generate cryptographically secure random bytes\n *\n * @param bits - Number of bits to generate (default: 256)\n * @param encoding - Output encoding (default: 'base64url')\n * @returns Random bytes in the specified encoding\n */\nexport function randomBytes(bits: number = 256, encoding: Encoding = 'base64url'): string | Uint8Array {\n\tconst bytes = Math.ceil(bits / 8);\n\tconst randomBytesArray = new Uint8Array(bytes);\n\tcrypto.getRandomValues(randomBytesArray);\n\treturn fromBytes(randomBytesArray, encoding);\n}\n\n/**\n * Generate a random private key\n */\nexport function generatePrivateKey(curve: CurveType = 'secp256k1', encoding: Encoding = 'base64url'): string | Uint8Array {\n\tlet keyBytes: Uint8Array;\n\n\tswitch (curve) {\n\t\tcase 'secp256k1':\n\t\t\tkeyBytes = secp256k1.utils.randomPrivateKey();\n\t\t\tbreak;\n\t\tcase 'p256':\n\t\t\tkeyBytes = p256.utils.randomPrivateKey();\n\t\t\tbreak;\n\t\tcase 'ed25519':\n\t\t\tkeyBytes = ed25519.utils.randomPrivateKey();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow new Error(`Unsupported curve: ${curve}`);\n\t}\n\n\treturn fromBytes(keyBytes, encoding);\n}\n\n/**\n * Get public key from private key\n */\nexport function getPublicKey(\n\tprivateKey: string | Uint8Array,\n\tcurve: CurveType = 'secp256k1',\n\tkeyEncoding: Encoding = 'base64url',\n\toutputEncoding: Encoding = 'base64url'\n): string | Uint8Array {\n\tconst keyBytes = toBytes(privateKey, keyEncoding);\n\n\tlet pubBytes: Uint8Array;\n\n\tswitch (curve) {\n\t\tcase 'secp256k1':\n\t\t\tpubBytes = secp256k1.getPublicKey(keyBytes);\n\t\t\tbreak;\n\t\tcase 'p256':\n\t\t\tpubBytes = p256.getPublicKey(keyBytes);\n\t\t\tbreak;\n\t\tcase 'ed25519':\n\t\t\tpubBytes = ed25519.getPublicKey(keyBytes);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow new Error(`Unsupported curve: ${curve}`);\n\t}\n\n\treturn fromBytes(pubBytes, outputEncoding);\n}\n\n","/**\n * Quereus Plugin Entry Point for Crypto Functions\n *\n * This module provides the plugin registration following Quereus 0.4.5 format.\n * All metadata is in package.json - no manifest export needed.\n */\n\nimport type { Database, FunctionFlags, SqlValue } from '@quereus/quereus';\nimport { digest, sign, verify, hashMod, randomBytes } from './crypto.js';\n\n/**\n * Plugin registration function\n * This is called by Quereus when the plugin is loaded\n */\nexport default function register(_db: Database, _config: Record<string, SqlValue> = {}) {\n\t// Register crypto functions with Quereus\n\tconst functions = [\n\t\t{\n\t\t\tschema: {\n\t\t\t\tname: 'digest',\n\t\t\t\tnumArgs: -1, // Variable arguments: data, algorithm?, inputEncoding?, outputEncoding?\n\t\t\t\tflags: 1 as FunctionFlags, // UTF8\n\t\t\t\treturnType: { typeClass: 'scalar' as const, sqlType: 'TEXT' },\n\t\t\t\timplementation: (...args: SqlValue[]) => {\n\t\t\t\t\tconst [data, algorithm = 'sha256', inputEncoding = 'base64url', outputEncoding = 'base64url'] = args;\n\t\t\t\t\treturn digest(data as string, algorithm as any, inputEncoding as any, outputEncoding as any);\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tschema: {\n\t\t\t\tname: 'sign',\n\t\t\t\tnumArgs: -1, // Variable arguments: data, privateKey, curve?, inputEncoding?, keyEncoding?, outputEncoding?\n\t\t\t\tflags: 1 as FunctionFlags,\n\t\t\t\treturnType: { typeClass: 'scalar' as const, sqlType: 'TEXT' },\n\t\t\t\timplementation: (...args: SqlValue[]) => {\n\t\t\t\t\tconst [data, privateKey, curve = 'secp256k1', inputEncoding = 'base64url', keyEncoding = 'base64url', outputEncoding = 'base64url'] = args;\n\t\t\t\t\treturn sign(data as string, privateKey as string, curve as any, inputEncoding as any, keyEncoding as any, outputEncoding as any);\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tschema: {\n\t\t\t\tname: 'verify',\n\t\t\t\tnumArgs: -1, // Variable arguments: data, signature, publicKey, curve?, inputEncoding?, sigEncoding?, keyEncoding?\n\t\t\t\tflags: 1 as FunctionFlags,\n\t\t\t\treturnType: { typeClass: 'scalar' as const, sqlType: 'INTEGER' },\n\t\t\t\timplementation: (...args: SqlValue[]) => {\n\t\t\t\t\tconst [data, signature, publicKey, curve = 'secp256k1', inputEncoding = 'base64url', sigEncoding = 'base64url', keyEncoding = 'base64url'] = args;\n\t\t\t\t\tconst result = verify(data as string, signature as string, publicKey as string, curve as any, inputEncoding as any, sigEncoding as any, keyEncoding as any);\n\t\t\t\t\treturn result ? 1 : 0;\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tschema: {\n\t\t\t\tname: 'hash_mod',\n\t\t\t\tnumArgs: -1, // Variable arguments: data, bits, algorithm?, inputEncoding?\n\t\t\t\tflags: 1 as FunctionFlags,\n\t\t\t\treturnType: { typeClass: 'scalar' as const, sqlType: 'INTEGER' },\n\t\t\t\timplementation: (...args: SqlValue[]) => {\n\t\t\t\t\tconst [data, bits, algorithm = 'sha256', inputEncoding = 'base64url'] = args;\n\t\t\t\t\treturn hashMod(data as string, bits as number, algorithm as any, inputEncoding as any);\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tschema: {\n\t\t\t\tname: 'random_bytes',\n\t\t\t\tnumArgs: -1, // Variable arguments: bits?, encoding?\n\t\t\t\tflags: 1 as FunctionFlags,\n\t\t\t\treturnType: { typeClass: 'scalar' as const, sqlType: 'TEXT' },\n\t\t\t\timplementation: (...args: SqlValue[]) => {\n\t\t\t\t\tconst [bits = 256, encoding = 'base64url'] = args;\n\t\t\t\t\treturn randomBytes(bits as number, encoding as any);\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t];\n\n\treturn {\n\t\tfunctions,\n\t\tvtables: [],\n\t\tcollations: [],\n\t};\n}\n\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@optimystic/quereus-plugin-crypto",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Quereus plugin providing cryptographic functions (digest, sign, verify)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./plugin": {
|
|
14
|
+
"import": "./dist/plugin.js",
|
|
15
|
+
"types": "./dist/plugin.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsup --watch",
|
|
26
|
+
"clean": "rm -rf dist",
|
|
27
|
+
"typecheck": "tsc --noEmit"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@quereus/quereus": "^0.4.5",
|
|
31
|
+
"@types/node": "^24.0.12",
|
|
32
|
+
"aegir": "^47.0.20",
|
|
33
|
+
"tsup": "^8.5.0",
|
|
34
|
+
"typescript": "^5.8.3"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@noble/curves": "^1.9.6",
|
|
38
|
+
"@noble/hashes": "^1.8.0",
|
|
39
|
+
"uint8arrays": "^5.1.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@quereus/quereus": "^0.4.5"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"quereus": "^0.4.5"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"quereus-plugin",
|
|
49
|
+
"quereus",
|
|
50
|
+
"crypto",
|
|
51
|
+
"cryptography",
|
|
52
|
+
"digest",
|
|
53
|
+
"hash",
|
|
54
|
+
"signature",
|
|
55
|
+
"sign",
|
|
56
|
+
"verify",
|
|
57
|
+
"secp256k1",
|
|
58
|
+
"ed25519",
|
|
59
|
+
"p256"
|
|
60
|
+
],
|
|
61
|
+
"author": "Optimystic Team",
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"quereus": {
|
|
64
|
+
"provides": {
|
|
65
|
+
"functions": ["digest", "sign", "verify", "hash_mod", "random_bytes"],
|
|
66
|
+
"vtables": [],
|
|
67
|
+
"collations": []
|
|
68
|
+
},
|
|
69
|
+
"settings": []
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/crypto.ts
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic Functions for Quereus
|
|
3
|
+
*
|
|
4
|
+
* Idiomatic ES module exports with base64url as default encoding.
|
|
5
|
+
* All functions accept and return base64url strings by default for SQL compatibility.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { sha256, sha512 } from '@noble/hashes/sha2';
|
|
9
|
+
import { blake3 } from '@noble/hashes/blake3';
|
|
10
|
+
import { concatBytes, utf8ToBytes } from '@noble/hashes/utils';
|
|
11
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
12
|
+
import { p256 } from '@noble/curves/nist';
|
|
13
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
14
|
+
import { hexToBytes, bytesToHex } from '@noble/curves/abstract/utils';
|
|
15
|
+
import { toString as uint8ArrayToString, fromString as uint8ArrayFromString } from 'uint8arrays';
|
|
16
|
+
|
|
17
|
+
// Type definitions
|
|
18
|
+
export type HashAlgorithm = 'sha256' | 'sha512' | 'blake3';
|
|
19
|
+
export type CurveType = 'secp256k1' | 'p256' | 'ed25519';
|
|
20
|
+
export type Encoding = 'base64url' | 'base64' | 'hex' | 'utf8' | 'bytes';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Convert input to Uint8Array, handling various encodings
|
|
24
|
+
*/
|
|
25
|
+
function toBytes(input: string | Uint8Array | null | undefined, encoding: Encoding = 'base64url'): Uint8Array {
|
|
26
|
+
if (input === null || input === undefined) {
|
|
27
|
+
return new Uint8Array(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (input instanceof Uint8Array) {
|
|
31
|
+
return input;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof input === 'string') {
|
|
35
|
+
switch (encoding) {
|
|
36
|
+
case 'base64url':
|
|
37
|
+
return uint8ArrayFromString(input, 'base64url');
|
|
38
|
+
case 'base64':
|
|
39
|
+
return uint8ArrayFromString(input, 'base64');
|
|
40
|
+
case 'hex':
|
|
41
|
+
return hexToBytes(input);
|
|
42
|
+
case 'utf8':
|
|
43
|
+
return utf8ToBytes(input);
|
|
44
|
+
default:
|
|
45
|
+
return uint8ArrayFromString(input, 'base64url');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw new Error('Invalid input type');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Convert Uint8Array to string in specified encoding
|
|
54
|
+
*/
|
|
55
|
+
function fromBytes(bytes: Uint8Array, encoding: Encoding = 'base64url'): string | Uint8Array {
|
|
56
|
+
switch (encoding) {
|
|
57
|
+
case 'base64url':
|
|
58
|
+
return uint8ArrayToString(bytes, 'base64url');
|
|
59
|
+
case 'base64':
|
|
60
|
+
return uint8ArrayToString(bytes, 'base64');
|
|
61
|
+
case 'hex':
|
|
62
|
+
return bytesToHex(bytes);
|
|
63
|
+
case 'utf8':
|
|
64
|
+
return uint8ArrayToString(bytes, 'utf8');
|
|
65
|
+
case 'bytes':
|
|
66
|
+
return bytes;
|
|
67
|
+
default:
|
|
68
|
+
return uint8ArrayToString(bytes, 'base64url');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Compute hash digest of input data
|
|
74
|
+
*
|
|
75
|
+
* @param data - Data to hash (base64url string or Uint8Array)
|
|
76
|
+
* @param algorithm - Hash algorithm (default: 'sha256')
|
|
77
|
+
* @param inputEncoding - Encoding of input string (default: 'base64url')
|
|
78
|
+
* @param outputEncoding - Encoding of output (default: 'base64url')
|
|
79
|
+
* @returns Hash digest in specified encoding
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* // Hash UTF-8 text, output as base64url
|
|
84
|
+
* const hash = digest('hello world', 'sha256', 'utf8');
|
|
85
|
+
*
|
|
86
|
+
* // Hash base64url data with SHA-512
|
|
87
|
+
* const hash2 = digest('SGVsbG8', 'sha512');
|
|
88
|
+
*
|
|
89
|
+
* // Get raw bytes
|
|
90
|
+
* const bytes = digest('data', 'blake3', 'utf8', 'bytes');
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export function digest(
|
|
94
|
+
data: string | Uint8Array,
|
|
95
|
+
algorithm: HashAlgorithm = 'sha256',
|
|
96
|
+
inputEncoding: Encoding = 'base64url',
|
|
97
|
+
outputEncoding: Encoding = 'base64url'
|
|
98
|
+
): string | Uint8Array {
|
|
99
|
+
const bytes = toBytes(data, inputEncoding);
|
|
100
|
+
|
|
101
|
+
let hashBytes: Uint8Array;
|
|
102
|
+
switch (algorithm) {
|
|
103
|
+
case 'sha256':
|
|
104
|
+
hashBytes = sha256(bytes);
|
|
105
|
+
break;
|
|
106
|
+
case 'sha512':
|
|
107
|
+
hashBytes = sha512(bytes);
|
|
108
|
+
break;
|
|
109
|
+
case 'blake3':
|
|
110
|
+
hashBytes = blake3(bytes);
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
throw new Error(`Unsupported hash algorithm: ${algorithm}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return fromBytes(hashBytes, outputEncoding);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Hash data and return modulo of specified bit length
|
|
121
|
+
* Useful for generating fixed-size hash values (e.g., 16-bit, 32-bit)
|
|
122
|
+
*
|
|
123
|
+
* @param data - Data to hash
|
|
124
|
+
* @param bits - Number of bits for the result (e.g., 16 for 16-bit hash)
|
|
125
|
+
* @param algorithm - Hash algorithm (default: 'sha256')
|
|
126
|
+
* @param inputEncoding - Encoding of input string (default: 'base64url')
|
|
127
|
+
* @returns Integer hash value modulo 2^bits
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* // Get 16-bit hash (0-65535)
|
|
132
|
+
* const hash16 = hashMod('hello', 16, 'sha256', 'utf8');
|
|
133
|
+
*
|
|
134
|
+
* // Get 32-bit hash
|
|
135
|
+
* const hash32 = hashMod('world', 32, 'sha256', 'utf8');
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export function hashMod(
|
|
139
|
+
data: string | Uint8Array,
|
|
140
|
+
bits: number,
|
|
141
|
+
algorithm: HashAlgorithm = 'sha256',
|
|
142
|
+
inputEncoding: Encoding = 'base64url'
|
|
143
|
+
): number {
|
|
144
|
+
if (bits <= 0 || bits > 53) {
|
|
145
|
+
throw new Error('Bits must be between 1 and 53 (JavaScript safe integer limit)');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const hashBytes = toBytes(digest(data, algorithm, inputEncoding, 'base64url') as string, 'base64url');
|
|
149
|
+
|
|
150
|
+
// Take first 8 bytes and convert to number
|
|
151
|
+
const view = new DataView(hashBytes.buffer, hashBytes.byteOffset, Math.min(8, hashBytes.length));
|
|
152
|
+
const fullHash = view.getBigUint64(0, false); // big-endian
|
|
153
|
+
|
|
154
|
+
// Modulo by 2^bits
|
|
155
|
+
const modulus = BigInt(2) ** BigInt(bits);
|
|
156
|
+
const result = fullHash % modulus;
|
|
157
|
+
|
|
158
|
+
return Number(result);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Sign data with a private key
|
|
163
|
+
*
|
|
164
|
+
* @param data - Data to sign (typically a hash)
|
|
165
|
+
* @param privateKey - Private key (base64url string or Uint8Array)
|
|
166
|
+
* @param curve - Elliptic curve (default: 'secp256k1')
|
|
167
|
+
* @param inputEncoding - Encoding of data input (default: 'base64url')
|
|
168
|
+
* @param keyEncoding - Encoding of private key (default: 'base64url')
|
|
169
|
+
* @param outputEncoding - Encoding of signature output (default: 'base64url')
|
|
170
|
+
* @returns Signature in specified encoding
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```typescript
|
|
174
|
+
* // Sign a hash with secp256k1
|
|
175
|
+
* const sig = sign(hashData, privateKey);
|
|
176
|
+
*
|
|
177
|
+
* // Sign with Ed25519
|
|
178
|
+
* const sig2 = sign(hashData, privateKey, 'ed25519');
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export function sign(
|
|
182
|
+
data: string | Uint8Array,
|
|
183
|
+
privateKey: string | Uint8Array,
|
|
184
|
+
curve: CurveType = 'secp256k1',
|
|
185
|
+
inputEncoding: Encoding = 'base64url',
|
|
186
|
+
keyEncoding: Encoding = 'base64url',
|
|
187
|
+
outputEncoding: Encoding = 'base64url'
|
|
188
|
+
): string | Uint8Array {
|
|
189
|
+
const dataBytes = toBytes(data, inputEncoding);
|
|
190
|
+
const keyBytes = toBytes(privateKey, keyEncoding);
|
|
191
|
+
|
|
192
|
+
let sigBytes: Uint8Array;
|
|
193
|
+
|
|
194
|
+
switch (curve) {
|
|
195
|
+
case 'secp256k1': {
|
|
196
|
+
const sig = secp256k1.sign(dataBytes, keyBytes, { lowS: true });
|
|
197
|
+
sigBytes = sig.toCompactRawBytes();
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case 'p256': {
|
|
201
|
+
const sig = p256.sign(dataBytes, keyBytes, { lowS: true });
|
|
202
|
+
sigBytes = sig.toCompactRawBytes();
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case 'ed25519': {
|
|
206
|
+
sigBytes = ed25519.sign(dataBytes, keyBytes);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
default:
|
|
210
|
+
throw new Error(`Unsupported curve: ${curve}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return fromBytes(sigBytes, outputEncoding);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Verify a signature
|
|
218
|
+
*
|
|
219
|
+
* @param data - Data that was signed
|
|
220
|
+
* @param signature - Signature to verify
|
|
221
|
+
* @param publicKey - Public key
|
|
222
|
+
* @param curve - Elliptic curve (default: 'secp256k1')
|
|
223
|
+
* @param inputEncoding - Encoding of data input (default: 'base64url')
|
|
224
|
+
* @param sigEncoding - Encoding of signature (default: 'base64url')
|
|
225
|
+
* @param keyEncoding - Encoding of public key (default: 'base64url')
|
|
226
|
+
* @returns true if signature is valid, false otherwise
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```typescript
|
|
230
|
+
* // Verify a signature
|
|
231
|
+
* const isValid = verify(hashData, signature, publicKey);
|
|
232
|
+
*
|
|
233
|
+
* // Verify with Ed25519
|
|
234
|
+
* const isValid2 = verify(hashData, signature, publicKey, 'ed25519');
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
export function verify(
|
|
238
|
+
data: string | Uint8Array,
|
|
239
|
+
signature: string | Uint8Array,
|
|
240
|
+
publicKey: string | Uint8Array,
|
|
241
|
+
curve: CurveType = 'secp256k1',
|
|
242
|
+
inputEncoding: Encoding = 'base64url',
|
|
243
|
+
sigEncoding: Encoding = 'base64url',
|
|
244
|
+
keyEncoding: Encoding = 'base64url'
|
|
245
|
+
): boolean {
|
|
246
|
+
try {
|
|
247
|
+
const dataBytes = toBytes(data, inputEncoding);
|
|
248
|
+
const sigBytes = toBytes(signature, sigEncoding);
|
|
249
|
+
const keyBytes = toBytes(publicKey, keyEncoding);
|
|
250
|
+
|
|
251
|
+
switch (curve) {
|
|
252
|
+
case 'secp256k1': {
|
|
253
|
+
return secp256k1.verify(sigBytes, dataBytes, keyBytes);
|
|
254
|
+
}
|
|
255
|
+
case 'p256': {
|
|
256
|
+
return p256.verify(sigBytes, dataBytes, keyBytes);
|
|
257
|
+
}
|
|
258
|
+
case 'ed25519': {
|
|
259
|
+
return ed25519.verify(sigBytes, dataBytes, keyBytes);
|
|
260
|
+
}
|
|
261
|
+
default:
|
|
262
|
+
throw new Error(`Unsupported curve: ${curve}`);
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Generate cryptographically secure random bytes
|
|
271
|
+
*
|
|
272
|
+
* @param bits - Number of bits to generate (default: 256)
|
|
273
|
+
* @param encoding - Output encoding (default: 'base64url')
|
|
274
|
+
* @returns Random bytes in the specified encoding
|
|
275
|
+
*/
|
|
276
|
+
export function randomBytes(bits: number = 256, encoding: Encoding = 'base64url'): string | Uint8Array {
|
|
277
|
+
const bytes = Math.ceil(bits / 8);
|
|
278
|
+
const randomBytesArray = new Uint8Array(bytes);
|
|
279
|
+
crypto.getRandomValues(randomBytesArray);
|
|
280
|
+
return fromBytes(randomBytesArray, encoding);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Generate a random private key
|
|
285
|
+
*/
|
|
286
|
+
export function generatePrivateKey(curve: CurveType = 'secp256k1', encoding: Encoding = 'base64url'): string | Uint8Array {
|
|
287
|
+
let keyBytes: Uint8Array;
|
|
288
|
+
|
|
289
|
+
switch (curve) {
|
|
290
|
+
case 'secp256k1':
|
|
291
|
+
keyBytes = secp256k1.utils.randomPrivateKey();
|
|
292
|
+
break;
|
|
293
|
+
case 'p256':
|
|
294
|
+
keyBytes = p256.utils.randomPrivateKey();
|
|
295
|
+
break;
|
|
296
|
+
case 'ed25519':
|
|
297
|
+
keyBytes = ed25519.utils.randomPrivateKey();
|
|
298
|
+
break;
|
|
299
|
+
default:
|
|
300
|
+
throw new Error(`Unsupported curve: ${curve}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return fromBytes(keyBytes, encoding);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Get public key from private key
|
|
308
|
+
*/
|
|
309
|
+
export function getPublicKey(
|
|
310
|
+
privateKey: string | Uint8Array,
|
|
311
|
+
curve: CurveType = 'secp256k1',
|
|
312
|
+
keyEncoding: Encoding = 'base64url',
|
|
313
|
+
outputEncoding: Encoding = 'base64url'
|
|
314
|
+
): string | Uint8Array {
|
|
315
|
+
const keyBytes = toBytes(privateKey, keyEncoding);
|
|
316
|
+
|
|
317
|
+
let pubBytes: Uint8Array;
|
|
318
|
+
|
|
319
|
+
switch (curve) {
|
|
320
|
+
case 'secp256k1':
|
|
321
|
+
pubBytes = secp256k1.getPublicKey(keyBytes);
|
|
322
|
+
break;
|
|
323
|
+
case 'p256':
|
|
324
|
+
pubBytes = p256.getPublicKey(keyBytes);
|
|
325
|
+
break;
|
|
326
|
+
case 'ed25519':
|
|
327
|
+
pubBytes = ed25519.getPublicKey(keyBytes);
|
|
328
|
+
break;
|
|
329
|
+
default:
|
|
330
|
+
throw new Error(`Unsupported curve: ${curve}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return fromBytes(pubBytes, outputEncoding);
|
|
334
|
+
}
|
|
335
|
+
|