@pulse-compute/crypto 1.0.0-beta.4 → 1.0.0-beta.6
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 +89 -8
- package/as/digest-text.as.ts +34 -0
- package/as/pulse-hmac-as.ts +7 -1
- package/conformance/digest-text.json +38 -0
- package/conformance/hs256.json +85 -0
- package/dist/contracts.d.ts +34 -12
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +9 -3
- package/dist/contracts.js.map +1 -1
- package/dist/digest.d.ts +14 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +17 -0
- package/dist/digest.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/rsa.d.ts +58 -0
- package/dist/internal/rsa.d.ts.map +1 -0
- package/dist/internal/rsa.js +185 -0
- package/dist/internal/rsa.js.map +1 -0
- package/dist/internal/verification.d.ts +3 -3
- package/dist/internal/verification.d.ts.map +1 -1
- package/dist/internal/verification.js +2 -2
- package/dist/internal/verification.js.map +1 -1
- package/guests/es256-rustcrypto/README.md +43 -0
- package/guests/es256-rustcrypto/build.cjs +297 -0
- package/guests/es256-rustcrypto/prebuilt/es256-verifier.unoptimized.wasm +0 -0
- package/guests/es256-rustcrypto/prebuilt/es256-verifier.wasm +0 -0
- package/guests/es256-rustcrypto/probe/.cargo/config.toml +18 -0
- package/guests/es256-rustcrypto/probe/Cargo.lock +238 -0
- package/guests/es256-rustcrypto/probe/Cargo.toml +26 -0
- package/guests/es256-rustcrypto/probe/README.md +35 -0
- package/guests/es256-rustcrypto/probe/rust-toolchain.toml +5 -0
- package/guests/es256-rustcrypto/probe/src/lib.rs +108 -0
- package/guests/es256-rustcrypto/pulse.guest-unit.json +119 -0
- package/guests/es256-rustcrypto/source/.cargo/config.toml +18 -0
- package/guests/es256-rustcrypto/source/Cargo.lock +265 -0
- package/guests/es256-rustcrypto/source/Cargo.toml +23 -0
- package/guests/es256-rustcrypto/source/README.md +26 -0
- package/guests/es256-rustcrypto/source/bearssl/LICENSE.txt +21 -0
- package/guests/es256-rustcrypto/source/bearssl/UPSTREAM.json +49 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl.h +170 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_aead.h +1059 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_block.h +2618 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_ec.h +883 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_hash.h +1346 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_hmac.h +241 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_kdf.h +185 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_pem.h +294 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_prf.h +150 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_rand.h +397 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_rsa.h +1366 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_ssl.h +4296 -0
- package/guests/es256-rustcrypto/source/bearssl/inc/bearssl_x509.h +1397 -0
- package/guests/es256-rustcrypto/source/bearssl/src/codec/ccopy.c +44 -0
- package/guests/es256-rustcrypto/source/bearssl/src/codec/dec32be.c +38 -0
- package/guests/es256-rustcrypto/source/bearssl/src/codec/enc32be.c +38 -0
- package/guests/es256-rustcrypto/source/bearssl/src/config.h +229 -0
- package/guests/es256-rustcrypto/source/bearssl/src/inner.h +2532 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_add.c +46 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_bitlen.c +44 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_decmod.c +124 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_decode.c +57 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_decred.c +103 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_encode.c +79 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_fmont.c +60 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_iszero.c +39 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_moddiv.c +488 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_modpow.c +65 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_modpow2.c +160 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_montmul.c +93 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_mulacc.c +61 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_muladd.c +157 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_ninv31.c +39 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_reduce.c +66 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_rshift.c +47 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_sub.c +46 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i31_tmont.c +36 -0
- package/guests/es256-rustcrypto/source/bearssl/src/int/i32_div32.c +56 -0
- package/guests/es256-rustcrypto/source/bearssl/src/rsa/rsa_i31_priv.c +203 -0
- package/guests/es256-rustcrypto/source/bearssl/src/rsa/rsa_i31_pub.c +106 -0
- package/guests/es256-rustcrypto/source/bearssl/src/rsa/rsa_pkcs1_sig_pad.c +100 -0
- package/guests/es256-rustcrypto/source/build.rs +38 -0
- package/guests/es256-rustcrypto/source/freestanding/string.h +8 -0
- package/guests/es256-rustcrypto/source/rs256.c +103 -0
- package/guests/es256-rustcrypto/source/rust-toolchain.toml +5 -0
- package/guests/es256-rustcrypto/source/src/lib.rs +308 -0
- package/package.json +22 -4
- package/pulse.package.json +64 -0
- package/pulsewasm.compiler.cjs +89 -0
- package/pulsewasm.manifest.cjs +15 -0
- package/pulsewasm.native.cjs +79 -7
- package/src/provider.cjs +141 -2
- package/src/provider.d.cts +6 -0
package/src/provider.cjs
CHANGED
|
@@ -19,7 +19,7 @@ function bindJavascriptDigestMac(algorithms, subtle = globalThis.crypto && globa
|
|
|
19
19
|
|| algorithms.includes('HMAC-SHA256') && (typeof subtle.sign !== 'function' || typeof subtle.importKey !== 'function')) throw new TypeError('Selected Web Crypto byte realization is unavailable.');
|
|
20
20
|
const bytes = {};
|
|
21
21
|
if (algorithms.includes('SHA-256')) bytes.sha256 = async (input) => {
|
|
22
|
-
const data = snapshot(input,
|
|
22
|
+
const data = snapshot(input, 2097152);
|
|
23
23
|
try { return result(await subtle.digest({ name: 'SHA-256' }, data)); }
|
|
24
24
|
finally { data.fill(0); }
|
|
25
25
|
};
|
|
@@ -40,4 +40,143 @@ function bindJavascriptDigestMac(algorithms, subtle = globalThis.crypto && globa
|
|
|
40
40
|
algorithms: Object.freeze(algorithms.map((algorithm) => Object.freeze({ algorithm, realization: 'runtime-builtin', implementation: IMPLEMENTATIONS[algorithm], available: true, automaticFallback: false })))
|
|
41
41
|
}) });
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
const DIGEST_TEXT_MAX_BYTES = 2097152;
|
|
44
|
+
const digestFailures = Object.freeze(Object.fromEntries(
|
|
45
|
+
['invalid-text', 'too-large', 'unavailable', 'realization-failure'].map(reason => [reason, Object.freeze({ status: 'failed', reason })])
|
|
46
|
+
));
|
|
47
|
+
// Count scalar UTF-8 bytes before allocating staging; do not replace surrogates.
|
|
48
|
+
function textDigestByteLength(text) {
|
|
49
|
+
if (typeof text !== 'string') return -1;
|
|
50
|
+
if (text.length > DIGEST_TEXT_MAX_BYTES) return -2;
|
|
51
|
+
let bytes = 0;
|
|
52
|
+
for (let i = 0; i < text.length; i++) {
|
|
53
|
+
const c = text.charCodeAt(i);
|
|
54
|
+
if (c >= 0xd800 && c <= 0xdbff) {
|
|
55
|
+
const low = text.charCodeAt(++i);
|
|
56
|
+
if (!(low >= 0xdc00 && low <= 0xdfff)) return -1;
|
|
57
|
+
bytes += 4;
|
|
58
|
+
} else if (c >= 0xdc00 && c <= 0xdfff) return -1;
|
|
59
|
+
else bytes += c < 0x80 ? 1 : c < 0x800 ? 2 : 3;
|
|
60
|
+
}
|
|
61
|
+
return bytes > DIGEST_TEXT_MAX_BYTES ? -2 : bytes;
|
|
62
|
+
}
|
|
63
|
+
function normalizeTextDigestResult(value) {
|
|
64
|
+
const failed = digestFailures['realization-failure'];
|
|
65
|
+
if (!value || typeof value !== 'object' || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return failed;
|
|
66
|
+
const properties = Object.getOwnPropertyDescriptors(value), keys = Reflect.ownKeys(properties);
|
|
67
|
+
if (keys.some(key => typeof key !== 'string' || !properties[key].enumerable || !Object.hasOwn(properties[key], 'value'))) return failed;
|
|
68
|
+
const status = properties.status?.value, reason = properties.reason?.value;
|
|
69
|
+
if (status === 'failed' && keys.length === 2 && typeof reason === 'string' && Object.hasOwn(digestFailures, reason)) return digestFailures[reason];
|
|
70
|
+
const sha256 = properties.sha256?.value, byteLength = properties.byteLength?.value;
|
|
71
|
+
if (status !== 'ok' || keys.length !== 3 || typeof sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(sha256)
|
|
72
|
+
|| !Number.isInteger(byteLength) || byteLength < 0 || byteLength > DIGEST_TEXT_MAX_BYTES) return failed;
|
|
73
|
+
return Object.freeze({ status: 'ok', sha256, byteLength });
|
|
74
|
+
}
|
|
75
|
+
function checkDigestCancellation(signal) {
|
|
76
|
+
if (signal?.aborted) throw signal.reason || new Error('Pulse digest invocation cancelled.');
|
|
77
|
+
}
|
|
78
|
+
async function executeTextDigest(effect, options = {}) {
|
|
79
|
+
if (!effect || effect.package !== '@pulse-compute/crypto' || effect.contractId !== 'pulse.crypto'
|
|
80
|
+
|| effect.providerKind !== 'crypto' || effect.operation !== 'digestText'
|
|
81
|
+
|| effect.kind !== 'crypto.digestText' || effect.capability !== 'crypto.digestText') throw new TypeError('Invalid Crypto digest effect authority.');
|
|
82
|
+
checkDigestCancellation(options.signal);
|
|
83
|
+
const text = effect.payload?.text, byteLength = textDigestByteLength(text);
|
|
84
|
+
if (byteLength < 0) return digestFailures[byteLength === -1 ? 'invalid-text' : 'too-large'];
|
|
85
|
+
const realization = options.cryptoTarget === 'javascript' ? 'runtime-builtin' : 'guest-source:pulse-hmac-as';
|
|
86
|
+
const bytes = options.cryptoVerifier?.bytes, algorithms = options.cryptoRealization?.algorithms;
|
|
87
|
+
if (typeof bytes?.sha256 !== 'function' || !Array.isArray(algorithms)
|
|
88
|
+
|| !algorithms.some(entry => entry.algorithm === 'SHA-256' && entry.available === true && entry.realization === realization)) return digestFailures.unavailable;
|
|
89
|
+
const data = new TextEncoder().encode(text);
|
|
90
|
+
let digest;
|
|
91
|
+
try {
|
|
92
|
+
digest = await bytes.sha256(data);
|
|
93
|
+
checkDigestCancellation(options.signal);
|
|
94
|
+
if (!(digest instanceof Uint8Array) || digest.length !== 32) return digestFailures['realization-failure'];
|
|
95
|
+
const sha256 = Array.from(digest, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
96
|
+
return Object.freeze({ status: 'ok', sha256, byteLength });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
checkDigestCancellation(options.signal);
|
|
99
|
+
return digestFailures['realization-failure'];
|
|
100
|
+
} finally { data.fill(0); if (digest instanceof Uint8Array) digest.fill(0); }
|
|
101
|
+
}
|
|
102
|
+
function createJavascriptTextDigest(options = {}) {
|
|
103
|
+
return (effect, execution = {}) => {
|
|
104
|
+
let selected;
|
|
105
|
+
try { selected = bindJavascriptDigestMac(['SHA-256'], options.digestSubtle === undefined ? globalThis.crypto?.subtle : options.digestSubtle); }
|
|
106
|
+
catch { selected = undefined; }
|
|
107
|
+
return executeTextDigest(effect, { signal: execution.signal, cryptoTarget: 'javascript',
|
|
108
|
+
cryptoVerifier: selected, cryptoRealization: selected?.realization });
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function bindJavascriptEs256Signer(subtle = globalThis.crypto?.subtle) {
|
|
113
|
+
if (!subtle || typeof subtle.importKey !== 'function' || typeof subtle.sign !== 'function'
|
|
114
|
+
|| typeof subtle.verify !== 'function') throw new TypeError('Selected ES256 signing realization is unavailable.');
|
|
115
|
+
const encode = bytes => {
|
|
116
|
+
let binary = ''; for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
117
|
+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
118
|
+
};
|
|
119
|
+
const invalidKey = () => Object.assign(new TypeError('Invalid P-256 signing key.'), { code: 'PULSE_CRYPTO_KEY_INVALID' });
|
|
120
|
+
return Object.freeze({ bytes: Object.freeze({ async es256Sign(key, data) {
|
|
121
|
+
if (!(key instanceof Uint8Array) || key.length !== 96) throw invalidKey();
|
|
122
|
+
if (!(data instanceof Uint8Array) || data.length > 16340) throw new TypeError('ES256 input exceeds its byte contract.');
|
|
123
|
+
const jwk = { kty: 'EC', crv: 'P-256', x: encode(key.subarray(0, 32)), y: encode(key.subarray(32, 64)), d: encode(key.subarray(64)) };
|
|
124
|
+
let signingKey, verifyingKey;
|
|
125
|
+
try {
|
|
126
|
+
signingKey = await subtle.importKey('jwk', jwk, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
|
|
127
|
+
verifyingKey = await subtle.importKey('jwk', { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y },
|
|
128
|
+
{ name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
|
|
129
|
+
} catch { throw invalidKey(); }
|
|
130
|
+
const signature = new Uint8Array(await subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, signingKey, data));
|
|
131
|
+
try {
|
|
132
|
+
if (signature.length !== 64 || !await subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, verifyingKey, signature, data)) {
|
|
133
|
+
throw invalidKey();
|
|
134
|
+
}
|
|
135
|
+
return signature.slice();
|
|
136
|
+
} finally { signature.fill(0); }
|
|
137
|
+
} }) });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Metadata parsing remains JWT-owned. This adapter normalizes only RSA integers.
|
|
141
|
+
async function rsaPrivateKeyBytes(components) {
|
|
142
|
+
return (await import('../dist/internal/rsa.js')).rsaKeyBytes(components, true);
|
|
143
|
+
}
|
|
144
|
+
function bindJavascriptRs256Signer(subtle = globalThis.crypto?.subtle) {
|
|
145
|
+
if (!subtle || typeof subtle.importKey !== 'function' || typeof subtle.sign !== 'function'
|
|
146
|
+
|| typeof subtle.verify !== 'function') throw new TypeError('Selected RS256 signing realization is unavailable.');
|
|
147
|
+
const invalidKey = () => Object.assign(new TypeError('Invalid RSA signing key.'), { code: 'PULSE_CRYPTO_KEY_INVALID' });
|
|
148
|
+
return Object.freeze({ bytes: Object.freeze({ async rs256Sign(key, data) {
|
|
149
|
+
const { rsaWireJwk, RSA_DATA_BYTES_MAXIMUM } = await import('../dist/internal/rsa.js');
|
|
150
|
+
if (!(data instanceof Uint8Array) || data.length > RSA_DATA_BYTES_MAXIMUM) throw new TypeError('RS256 input exceeds its byte contract.');
|
|
151
|
+
let jwk, signingKey, verifyingKey;
|
|
152
|
+
const algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };
|
|
153
|
+
try {
|
|
154
|
+
jwk = rsaWireJwk(key, true);
|
|
155
|
+
// Web Crypto implementations need not check every redundant CRT field.
|
|
156
|
+
// Check consistency explicitly at import. JavaScript BigInt has no
|
|
157
|
+
// constant-time guarantee; private exponentiation remains Web Crypto.
|
|
158
|
+
const integer = text => {
|
|
159
|
+
const binary = atob(text.replace(/-/g, '+').replace(/_/g, '/'));
|
|
160
|
+
let value = 0n; for (const byte of binary) value = value * 256n + BigInt(byte.charCodeAt(0));
|
|
161
|
+
return value;
|
|
162
|
+
};
|
|
163
|
+
const { n, e, d, p, q, dp, dq, qi } = Object.fromEntries(['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi'].map(name => [name, integer(jwk[name])]));
|
|
164
|
+
const k = key.length > 8 ? new DataView(key.buffer, key.byteOffset, 4).getUint32(0, true) : 0;
|
|
165
|
+
const halfMinimum = 1n << BigInt(k * 4 - 1);
|
|
166
|
+
if (p < halfMinimum || q < halfMinimum || !(p & q & 1n) || p === q || p*q !== n
|
|
167
|
+
|| d <= 0n || d >= n || d % (p-1n) !== dp || d % (q-1n) !== dq
|
|
168
|
+
|| e*dp % (p-1n) !== 1n || e*dq % (q-1n) !== 1n || qi <= 0n || qi >= p || q*qi % p !== 1n) throw invalidKey();
|
|
169
|
+
signingKey = await subtle.importKey('jwk', jwk, algorithm, false, ['sign']);
|
|
170
|
+
verifyingKey = await subtle.importKey('jwk', { kty: 'RSA', n: jwk.n, e: jwk.e }, algorithm, false, ['verify']);
|
|
171
|
+
} catch { throw invalidKey(); }
|
|
172
|
+
const signature = new Uint8Array(await subtle.sign(algorithm, signingKey, data));
|
|
173
|
+
try {
|
|
174
|
+
if (signature.length !== new DataView(key.buffer, key.byteOffset, 4).getUint32(0, true)
|
|
175
|
+
|| !await subtle.verify(algorithm, verifyingKey, signature, data)) throw invalidKey();
|
|
176
|
+
return signature.slice();
|
|
177
|
+
} finally { signature.fill(0); }
|
|
178
|
+
} }) });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = Object.freeze({ rsaPrivateKeyBytes, bindJavascriptRs256Signer, bindJavascriptEs256Signer, IMPLEMENTATIONS, bindJavascriptDigestMac, DIGEST_TEXT_MAX_BYTES,
|
|
182
|
+
textDigestByteLength, normalizeTextDigestResult, executeTextDigest, createJavascriptTextDigest });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TextDigestResult } from './digest.js';
|
|
2
|
+
export declare function normalizeTextDigestResult(value: unknown): TextDigestResult;
|
|
3
|
+
declare const provider: { rsaPrivateKeyBytes: typeof rsaPrivateKeyBytes; normalizeTextDigestResult: typeof normalizeTextDigestResult };
|
|
4
|
+
export default provider;
|
|
5
|
+
|
|
6
|
+
export declare function rsaPrivateKeyBytes(components: Readonly<Record<string, unknown>>): Promise<Uint8Array>;
|