@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/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
+