@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
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#![no_std]
|
|
2
|
+
|
|
3
|
+
use core::{panic::PanicInfo, slice};
|
|
4
|
+
use ecdsa::{
|
|
5
|
+
hazmat::{bits2field, verify_prehashed},
|
|
6
|
+
Signature,
|
|
7
|
+
};
|
|
8
|
+
use p256::{
|
|
9
|
+
elliptic_curve::sec1::FromEncodedPoint,
|
|
10
|
+
AffinePoint,
|
|
11
|
+
EncodedPoint,
|
|
12
|
+
NistP256,
|
|
13
|
+
ProjectivePoint,
|
|
14
|
+
};
|
|
15
|
+
use sha2::{Digest, Sha256};
|
|
16
|
+
|
|
17
|
+
type P256Signature = Signature<NistP256>;
|
|
18
|
+
|
|
19
|
+
const MEMORY_BYTES: u32 = 32 * 65_536;
|
|
20
|
+
const INVOCATION_REGION_START: u32 = 8 * 65_536;
|
|
21
|
+
const FRAME_MAGIC: u32 = 0x3253_4550;
|
|
22
|
+
const FRAME_VERSION: u32 = 2;
|
|
23
|
+
const FRAME_HEADER_BYTES: u32 = 64;
|
|
24
|
+
const FRAME_CAPACITY_BYTES: u32 = 16_640;
|
|
25
|
+
const FRAME_ALIGNMENT_BYTES: u32 = 16;
|
|
26
|
+
const FRAME_ALGORITHM_ES256: u32 = 1;
|
|
27
|
+
const P256_PUBLIC_KEY_BYTES: u32 = 64;
|
|
28
|
+
const ES256_SIGNATURE_BYTES: u32 = 64;
|
|
29
|
+
const ES256_SIGNING_INPUT_BYTES_MAXIMUM: u32 = 16_340;
|
|
30
|
+
|
|
31
|
+
const STATUS_VALID: i32 = 1;
|
|
32
|
+
const STATUS_INVALID_AUTHENTICATOR: i32 = 0;
|
|
33
|
+
const STATUS_INVALID_KEY: i32 = -1;
|
|
34
|
+
const STATUS_INVALID_INPUT: i32 = -2;
|
|
35
|
+
|
|
36
|
+
const HEADER_MAGIC: usize = 0;
|
|
37
|
+
const HEADER_VERSION: usize = 4;
|
|
38
|
+
const HEADER_LENGTH: usize = 8;
|
|
39
|
+
const HEADER_TOTAL_LENGTH: usize = 12;
|
|
40
|
+
const HEADER_ALGORITHM: usize = 16;
|
|
41
|
+
const HEADER_FLAGS: usize = 20;
|
|
42
|
+
const HEADER_SIGNING_INPUT_OFFSET: usize = 24;
|
|
43
|
+
const HEADER_SIGNING_INPUT_LENGTH: usize = 28;
|
|
44
|
+
const HEADER_PUBLIC_KEY_OFFSET: usize = 32;
|
|
45
|
+
const HEADER_PUBLIC_KEY_LENGTH: usize = 36;
|
|
46
|
+
const HEADER_SIGNATURE_OFFSET: usize = 40;
|
|
47
|
+
const HEADER_SIGNATURE_LENGTH: usize = 44;
|
|
48
|
+
const HEADER_RESERVED_0: usize = 48;
|
|
49
|
+
const HEADER_RESERVED_1: usize = 52;
|
|
50
|
+
const HEADER_RESERVED_2: usize = 56;
|
|
51
|
+
const HEADER_RESERVED_3: usize = 60;
|
|
52
|
+
|
|
53
|
+
#[derive(Clone, Copy)]
|
|
54
|
+
struct Span {
|
|
55
|
+
start: u32,
|
|
56
|
+
end: u32,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
impl Span {
|
|
60
|
+
fn checked(start: u32, length: u32, total_length: u32) -> Option<Self> {
|
|
61
|
+
if start < FRAME_HEADER_BYTES
|
|
62
|
+
|| start % FRAME_ALIGNMENT_BYTES != 0
|
|
63
|
+
{
|
|
64
|
+
return None;
|
|
65
|
+
}
|
|
66
|
+
let end = start.checked_add(length)?;
|
|
67
|
+
if end > total_length {
|
|
68
|
+
return None;
|
|
69
|
+
}
|
|
70
|
+
Some(Self { start, end })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn overlaps(self, other: Self) -> bool {
|
|
74
|
+
self.start < other.end && other.start < self.end
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[panic_handler]
|
|
79
|
+
fn panic(_info: &PanicInfo<'_>) -> ! {
|
|
80
|
+
loop {
|
|
81
|
+
core::hint::spin_loop();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fn read_u32_le(bytes: &[u8], offset: usize) -> u32 {
|
|
86
|
+
u32::from_le_bytes([
|
|
87
|
+
bytes[offset],
|
|
88
|
+
bytes[offset + 1],
|
|
89
|
+
bytes[offset + 2],
|
|
90
|
+
bytes[offset + 3],
|
|
91
|
+
])
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fn outer_frame_is_valid(frame_pointer: u32, frame_capacity: u32) -> bool {
|
|
95
|
+
if frame_capacity != FRAME_CAPACITY_BYTES
|
|
96
|
+
|| frame_pointer < INVOCATION_REGION_START
|
|
97
|
+
|| frame_pointer % FRAME_ALIGNMENT_BYTES != 0
|
|
98
|
+
{
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
match frame_pointer.checked_add(frame_capacity) {
|
|
102
|
+
Some(end) => end <= MEMORY_BYTES,
|
|
103
|
+
None => false,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fn header_is_valid(frame: &[u8], key_bytes: u32) -> Option<(Span, Span, Span)> {
|
|
108
|
+
signature_header(frame, key_bytes, ES256_SIGNATURE_BYTES, FRAME_ALGORITHM_ES256, ES256_SIGNING_INPUT_BYTES_MAXIMUM)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn signature_header(frame: &[u8], key_bytes: u32, signature_bytes: u32, algorithm: u32, input_max: u32) -> Option<(Span, Span, Span)> {
|
|
112
|
+
let total_length = read_u32_le(frame, HEADER_TOTAL_LENGTH);
|
|
113
|
+
if read_u32_le(frame, HEADER_MAGIC) != FRAME_MAGIC
|
|
114
|
+
|| read_u32_le(frame, HEADER_VERSION) != FRAME_VERSION
|
|
115
|
+
|| read_u32_le(frame, HEADER_LENGTH) != FRAME_HEADER_BYTES
|
|
116
|
+
|| total_length < 192
|
|
117
|
+
|| total_length > FRAME_CAPACITY_BYTES
|
|
118
|
+
|| total_length % FRAME_ALIGNMENT_BYTES != 0
|
|
119
|
+
|| read_u32_le(frame, HEADER_ALGORITHM) != algorithm
|
|
120
|
+
|| read_u32_le(frame, HEADER_FLAGS) != 0
|
|
121
|
+
|| read_u32_le(frame, HEADER_RESERVED_0) != 0
|
|
122
|
+
|| read_u32_le(frame, HEADER_RESERVED_1) != 0
|
|
123
|
+
|| read_u32_le(frame, HEADER_RESERVED_2) != 0
|
|
124
|
+
|| read_u32_le(frame, HEADER_RESERVED_3) != 0
|
|
125
|
+
{
|
|
126
|
+
return None;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let signing_input_length =
|
|
130
|
+
read_u32_le(frame, HEADER_SIGNING_INPUT_LENGTH);
|
|
131
|
+
let public_key_length = read_u32_le(frame, HEADER_PUBLIC_KEY_LENGTH);
|
|
132
|
+
let signature_length = read_u32_le(frame, HEADER_SIGNATURE_LENGTH);
|
|
133
|
+
if signing_input_length > input_max
|
|
134
|
+
|| public_key_length != key_bytes
|
|
135
|
+
|| signature_length != signature_bytes
|
|
136
|
+
{
|
|
137
|
+
return None;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let signing_input = Span::checked(
|
|
141
|
+
read_u32_le(frame, HEADER_SIGNING_INPUT_OFFSET),
|
|
142
|
+
signing_input_length,
|
|
143
|
+
total_length,
|
|
144
|
+
)?;
|
|
145
|
+
let public_key = Span::checked(
|
|
146
|
+
read_u32_le(frame, HEADER_PUBLIC_KEY_OFFSET),
|
|
147
|
+
public_key_length,
|
|
148
|
+
total_length,
|
|
149
|
+
)?;
|
|
150
|
+
let signature = Span::checked(
|
|
151
|
+
read_u32_le(frame, HEADER_SIGNATURE_OFFSET),
|
|
152
|
+
signature_length,
|
|
153
|
+
total_length,
|
|
154
|
+
)?;
|
|
155
|
+
|
|
156
|
+
if signing_input.start == public_key.start
|
|
157
|
+
|| signing_input.start == signature.start
|
|
158
|
+
|| public_key.start == signature.start
|
|
159
|
+
|| signing_input.overlaps(public_key)
|
|
160
|
+
|| signing_input.overlaps(signature)
|
|
161
|
+
|| public_key.overlaps(signature)
|
|
162
|
+
{
|
|
163
|
+
return None;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
Some((signing_input, public_key, signature))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn verify_es256(
|
|
170
|
+
signing_input: &[u8],
|
|
171
|
+
public_key_bytes: &[u8],
|
|
172
|
+
signature_bytes: &[u8],
|
|
173
|
+
) -> i32 {
|
|
174
|
+
let mut sec1_key = [0_u8; P256_PUBLIC_KEY_BYTES as usize + 1];
|
|
175
|
+
sec1_key[0] = 0x04;
|
|
176
|
+
sec1_key[1..].copy_from_slice(public_key_bytes);
|
|
177
|
+
|
|
178
|
+
let encoded_point = match EncodedPoint::from_bytes(sec1_key) {
|
|
179
|
+
Ok(value) => value,
|
|
180
|
+
Err(_) => return STATUS_INVALID_KEY,
|
|
181
|
+
};
|
|
182
|
+
sec1_key.fill(0);
|
|
183
|
+
|
|
184
|
+
let affine_point = match Option::<AffinePoint>::from(
|
|
185
|
+
AffinePoint::from_encoded_point(&encoded_point),
|
|
186
|
+
) {
|
|
187
|
+
Some(value) => value,
|
|
188
|
+
None => return STATUS_INVALID_KEY,
|
|
189
|
+
};
|
|
190
|
+
let signature = match P256Signature::from_slice(signature_bytes) {
|
|
191
|
+
Ok(value) => value,
|
|
192
|
+
Err(_) => return STATUS_INVALID_INPUT,
|
|
193
|
+
};
|
|
194
|
+
let digest = Sha256::digest(signing_input);
|
|
195
|
+
let digest_field = match bits2field::<NistP256>(&digest) {
|
|
196
|
+
Ok(value) => value,
|
|
197
|
+
Err(_) => return STATUS_INVALID_INPUT,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
match verify_prehashed::<NistP256>(
|
|
201
|
+
&ProjectivePoint::from(affine_point),
|
|
202
|
+
&digest_field,
|
|
203
|
+
&signature,
|
|
204
|
+
) {
|
|
205
|
+
Ok(()) => STATUS_VALID,
|
|
206
|
+
Err(_) => STATUS_INVALID_AUTHENTICATOR,
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// Verifies one ES256 request encoded in the frozen private invocation frame.
|
|
211
|
+
///
|
|
212
|
+
/// The caller owns and clears the fixed-capacity frame after capturing the
|
|
213
|
+
/// scalar result. This guest does not retain frame pointers or mutate input.
|
|
214
|
+
///
|
|
215
|
+
/// # Safety
|
|
216
|
+
///
|
|
217
|
+
/// The host must instantiate this module with the declared fixed, unshared
|
|
218
|
+
/// 32-page `env.memory`. This function validates the outer range before its
|
|
219
|
+
/// first dereference and validates every described field before field access.
|
|
220
|
+
#[no_mangle]
|
|
221
|
+
pub unsafe extern "C" fn pulse_crypto_es256_verify(
|
|
222
|
+
frame_pointer: u32,
|
|
223
|
+
frame_capacity: u32,
|
|
224
|
+
) -> i32 {
|
|
225
|
+
if !outer_frame_is_valid(frame_pointer, frame_capacity) {
|
|
226
|
+
return STATUS_INVALID_INPUT;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let frame = slice::from_raw_parts(
|
|
230
|
+
frame_pointer as *const u8,
|
|
231
|
+
frame_capacity as usize,
|
|
232
|
+
);
|
|
233
|
+
let (signing_input, public_key, signature) =
|
|
234
|
+
match header_is_valid(frame, P256_PUBLIC_KEY_BYTES) {
|
|
235
|
+
Some(value) => value,
|
|
236
|
+
None => return STATUS_INVALID_INPUT,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
verify_es256(
|
|
240
|
+
&frame[signing_input.start as usize..signing_input.end as usize],
|
|
241
|
+
&frame[public_key.start as usize..public_key.end as usize],
|
|
242
|
+
&frame[signature.start as usize..signature.end as usize],
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
/// Sign one ES256 request using x || y || d private-key bytes. The frame
|
|
248
|
+
/// follows the verification envelope with a 96-byte key and writable output.
|
|
249
|
+
/// Input spans must not overlap. Callers clear the frame and borrowed Rust
|
|
250
|
+
/// stack after capturing the signature; this function retains no pointers.
|
|
251
|
+
#[no_mangle]
|
|
252
|
+
pub unsafe extern "C" fn pulse_crypto_es256_sign(frame_pointer: u32, frame_capacity: u32) -> i32 {
|
|
253
|
+
use p256::ecdsa::{signature::hazmat::PrehashSigner, SigningKey};
|
|
254
|
+
if !outer_frame_is_valid(frame_pointer, frame_capacity) { return STATUS_INVALID_INPUT; }
|
|
255
|
+
let frame = slice::from_raw_parts_mut(frame_pointer as *mut u8, frame_capacity as usize);
|
|
256
|
+
let (input, key, output) = match header_is_valid(frame, 96) {
|
|
257
|
+
Some(value) => value, None => return STATUS_INVALID_INPUT,
|
|
258
|
+
};
|
|
259
|
+
// Clear output before validating the key so failed calls cannot expose a
|
|
260
|
+
// prior signature. Header validation already proved disjoint spans.
|
|
261
|
+
frame[output.start as usize..output.end as usize].fill(0);
|
|
262
|
+
let key_start = key.start as usize;
|
|
263
|
+
let signer = match SigningKey::from_slice(&frame[key_start + 64..key.end as usize]) {
|
|
264
|
+
Ok(value) => value, Err(_) => return STATUS_INVALID_KEY,
|
|
265
|
+
};
|
|
266
|
+
let public = signer.verifying_key().to_encoded_point(false);
|
|
267
|
+
if public.as_bytes()[1..] != frame[key_start..key_start + 64] { return STATUS_INVALID_KEY; }
|
|
268
|
+
let digest = Sha256::digest(&frame[input.start as usize..input.end as usize]);
|
|
269
|
+
// RustCrypto's deterministic RFC6979 signing requires no host randomness.
|
|
270
|
+
let signature: P256Signature = match signer.sign_prehash(&digest) {
|
|
271
|
+
Ok(value) => value, Err(_) => return STATUS_INVALID_KEY,
|
|
272
|
+
};
|
|
273
|
+
frame[output.start as usize..output.end as usize].copy_from_slice(&signature.to_bytes());
|
|
274
|
+
STATUS_VALID
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
extern "C" {
|
|
279
|
+
fn pulse_rs256(key: *const u8, key_len: usize, hash: *const u8,
|
|
280
|
+
signature: *mut u8, modulus_len: usize, sign: i32) -> i32;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
unsafe fn rsa_frame(pointer: u32, capacity: u32, sign: bool) -> i32 {
|
|
284
|
+
if !outer_frame_is_valid(pointer, capacity) { return STATUS_INVALID_INPUT; }
|
|
285
|
+
let frame = slice::from_raw_parts_mut(pointer as *mut u8, capacity as usize);
|
|
286
|
+
let k = read_u32_le(frame, HEADER_SIGNATURE_LENGTH);
|
|
287
|
+
if !matches!(k, 256 | 384 | 512) { return STATUS_INVALID_INPUT; }
|
|
288
|
+
let key_len = if sign { 8 + k * 9 / 2 } else { 8 + k };
|
|
289
|
+
let (input, key, output) = match signature_header(frame, key_len, k, 2, 12_288) {
|
|
290
|
+
Some(value) => value, None => return STATUS_INVALID_INPUT,
|
|
291
|
+
};
|
|
292
|
+
if sign { frame[output.start as usize..output.end as usize].fill(0); }
|
|
293
|
+
let hash = Sha256::digest(&frame[input.start as usize..input.end as usize]);
|
|
294
|
+
pulse_rs256(frame.as_ptr().add(key.start as usize), key_len as usize,
|
|
295
|
+
hash.as_ptr(), frame.as_mut_ptr().add(output.start as usize), k as usize, sign as i32)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/// RS256 uses the same checked v2 envelope, algorithm code 2, variable-width
|
|
299
|
+
/// key/signature spans, and a 12288-byte data bound. Caller clears frame/stack.
|
|
300
|
+
#[no_mangle]
|
|
301
|
+
pub unsafe extern "C" fn pulse_crypto_rs256_sign(pointer: u32, capacity: u32) -> i32 {
|
|
302
|
+
rsa_frame(pointer, capacity, true)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#[no_mangle]
|
|
306
|
+
pub unsafe extern "C" fn pulse_crypto_rs256_verify(pointer: u32, capacity: u32) -> i32 {
|
|
307
|
+
rsa_frame(pointer, capacity, false)
|
|
308
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulse-compute/crypto",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.6",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^22.14.0 || ^24.0.0"
|
|
7
7
|
},
|
|
8
|
-
"description": "Provider-neutral cryptographic verification
|
|
8
|
+
"description": "Provider-neutral cryptographic verification and bounded exact-text digests for Pulse.",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"sideEffects": false,
|
|
11
11
|
"exports": {
|
|
@@ -15,14 +15,22 @@
|
|
|
15
15
|
"default": "./dist/index.js"
|
|
16
16
|
},
|
|
17
17
|
"./pulsewasm-native": "./pulsewasm.native.cjs",
|
|
18
|
-
"./provider": "./src/provider.cjs"
|
|
18
|
+
"./provider": "./src/provider.cjs",
|
|
19
|
+
"./pulsewasm/manifest": "./pulsewasm.manifest.cjs",
|
|
20
|
+
"./pulsewasm/compiler": "./pulsewasm.compiler.cjs"
|
|
19
21
|
},
|
|
20
22
|
"files": [
|
|
21
23
|
"as",
|
|
24
|
+
"guests",
|
|
22
25
|
"dist",
|
|
23
26
|
"pulsewasm.native.cjs",
|
|
24
27
|
"README.md",
|
|
25
28
|
"src/provider.cjs",
|
|
29
|
+
"src/provider.d.cts",
|
|
30
|
+
"pulse.package.json",
|
|
31
|
+
"pulsewasm.manifest.cjs",
|
|
32
|
+
"pulsewasm.compiler.cjs",
|
|
33
|
+
"conformance",
|
|
26
34
|
"LICENSE",
|
|
27
35
|
"NOTICE"
|
|
28
36
|
],
|
|
@@ -37,9 +45,19 @@
|
|
|
37
45
|
"bugs": {
|
|
38
46
|
"url": "https://github.com/pulse-compute/pulse/issues"
|
|
39
47
|
},
|
|
40
|
-
"homepage": "https://pulsecompute.io/v1.0.0-beta.
|
|
48
|
+
"homepage": "https://pulsecompute.io/v1.0.0-beta.6/packages/crypto/",
|
|
41
49
|
"main": "./dist/index.js",
|
|
42
50
|
"types": "./dist/index.d.ts",
|
|
51
|
+
"pulse": {
|
|
52
|
+
"contract": "./pulse.package.json"
|
|
53
|
+
},
|
|
54
|
+
"pulsewasm": {
|
|
55
|
+
"manifest": "./pulsewasm.manifest.cjs"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@pulse-compute/runtime": "1.0.0-beta.6",
|
|
59
|
+
"@pulse-compute/wasm-contracts": "1.0.0-beta.6"
|
|
60
|
+
},
|
|
43
61
|
"scripts": {
|
|
44
62
|
"build": "tsc -b",
|
|
45
63
|
"test": "corepack pnpm --dir ../.. exec vitest run packages/crypto/test",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "pulse.package-contract.v1",
|
|
3
|
+
"kind": "pulse.package-contract",
|
|
4
|
+
"contractId": "pulse.crypto",
|
|
5
|
+
"npmPackage": "@pulse-compute/crypto",
|
|
6
|
+
"description": "Bounded exact-text SHA-256 through an execution-owned effect.",
|
|
7
|
+
"authoring": {
|
|
8
|
+
"namespace": "crypto",
|
|
9
|
+
"import": "@pulse-compute/crypto",
|
|
10
|
+
"symbols": [
|
|
11
|
+
"digestText"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"ownership": {
|
|
15
|
+
"profileFragment": "crypto",
|
|
16
|
+
"helperImports": [],
|
|
17
|
+
"reExportImports": []
|
|
18
|
+
},
|
|
19
|
+
"composition": {
|
|
20
|
+
"status": "reserved",
|
|
21
|
+
"helperSymbol": "crypto",
|
|
22
|
+
"profileToken": "opaque-direct-use",
|
|
23
|
+
"reasonCode": "PULSE_PACKAGE_COMPOSITION_HELPER_NOT_REALIZED"
|
|
24
|
+
},
|
|
25
|
+
"targets": {
|
|
26
|
+
"native": {
|
|
27
|
+
"status": "provider-dependent",
|
|
28
|
+
"realization": "canonical-package-lowering",
|
|
29
|
+
"lowerableSubpath": "@pulse-compute/crypto",
|
|
30
|
+
"providerRequirements": [
|
|
31
|
+
"crypto.digestText"
|
|
32
|
+
],
|
|
33
|
+
"reasonCode": "PULSE_PACKAGE_PROVIDER_CAPABILITY_REQUIRED"
|
|
34
|
+
},
|
|
35
|
+
"javascript": {
|
|
36
|
+
"status": "supported",
|
|
37
|
+
"realization": "javascript-package-runtime",
|
|
38
|
+
"entry": "./dist/index.js",
|
|
39
|
+
"providerRequirements": [
|
|
40
|
+
"crypto.digestText"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"lowering": {
|
|
45
|
+
"manifest": "./pulsewasm.manifest.cjs",
|
|
46
|
+
"trust": "first-party",
|
|
47
|
+
"canonicalOperationVersion": "pulse.canonical-package-operation.v1",
|
|
48
|
+
"bundleVersion": "pulse.package-lowering-bundle.v1",
|
|
49
|
+
"providerRequirementVersion": "pulse.provider-requirement-record.v1"
|
|
50
|
+
},
|
|
51
|
+
"conformance": {
|
|
52
|
+
"status": "target-overlap",
|
|
53
|
+
"fixtureRoots": [
|
|
54
|
+
"./conformance"
|
|
55
|
+
],
|
|
56
|
+
"semanticCases": [
|
|
57
|
+
"crypto.digestText"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"docs": {
|
|
61
|
+
"readme": "./README.md",
|
|
62
|
+
"contract": "./pulse.package.json"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { normalizeDiagnostic, sourceLoc } = require('@pulse-compute/wasm-contracts/diagnostics');
|
|
3
|
+
const { CANONICAL_PACKAGE_EFFECT_VERSION } = require('@pulse-compute/wasm-contracts/handler/canonical-runtime');
|
|
4
|
+
const { PACKAGE_CRYPTO_REQUIREMENT_VERSION } = require('@pulse-compute/wasm-contracts/package/package-contract');
|
|
5
|
+
const PACKAGE = '@pulse-compute/crypto';
|
|
6
|
+
function buildCryptoLoweringPlan(input = {}) {
|
|
7
|
+
const ts = input.typescript || require('typescript');
|
|
8
|
+
const source = input.sourceFile || ts.createSourceFile(input.sourcePath || 'index.ts', input.sourceText || '', ts.ScriptTarget.ES2022, true);
|
|
9
|
+
const namespaces = new Set(), direct = new Set(), diagnostics = [], effects = [];
|
|
10
|
+
const error = (node, suffix, message) => diagnostics.push(normalizeDiagnostic({
|
|
11
|
+
phase: 'crypto-lowering', severity: 'error', code: `PULSEWASM_CRYPTO_DIGEST_${suffix}`, message,
|
|
12
|
+
hint: 'Await crypto.digestText(ctx, text) into a local variable or use it as a direct keyed ctx.parallel member.', loc: sourceLoc(source, node)
|
|
13
|
+
}));
|
|
14
|
+
for (const statement of source.statements) {
|
|
15
|
+
if (!ts.isImportDeclaration(statement) || statement.moduleSpecifier.text !== PACKAGE || statement.importClause?.isTypeOnly) continue;
|
|
16
|
+
if (statement.importClause?.name) namespaces.add(statement.importClause.name.text);
|
|
17
|
+
const named = statement.importClause?.namedBindings;
|
|
18
|
+
if (named && ts.isNamedImports(named)) for (const entry of named.elements) {
|
|
19
|
+
if (entry.isTypeOnly) continue;
|
|
20
|
+
const name = (entry.propertyName || entry.name).text;
|
|
21
|
+
if (name === 'crypto') namespaces.add(entry.name.text);
|
|
22
|
+
if (name === 'digestText') direct.add(entry.name.text);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function checkBinding(name) {
|
|
26
|
+
if (ts.isIdentifier(name)) {
|
|
27
|
+
if (namespaces.has(name.text) || direct.has(name.text)) error(name, 'SHADOWED', 'Imported Crypto bindings cannot be shadowed.');
|
|
28
|
+
} else if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
29
|
+
for (const element of name.elements) if (ts.isBindingElement(element)) checkBinding(element.name);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function isDigest(expression) {
|
|
33
|
+
return ts.isIdentifier(expression) && direct.has(expression.text)
|
|
34
|
+
|| ts.isPropertyAccessExpression(expression) && expression.name.text === 'digestText'
|
|
35
|
+
&& ts.isIdentifier(expression.expression) && namespaces.has(expression.expression.text);
|
|
36
|
+
}
|
|
37
|
+
// Verification remains an ordinary JavaScript API. Its existing facade
|
|
38
|
+
// aliases and helpers must not acquire digest-specific authoring restrictions.
|
|
39
|
+
let hasDigestUse = false;
|
|
40
|
+
function findDigestUse(node) {
|
|
41
|
+
if (ts.isImportDeclaration(node) || ts.isTypeNode(node)) return;
|
|
42
|
+
if (isDigest(node)
|
|
43
|
+
|| ts.isPropertyAccessExpression(node) && node.name.text === 'digestText'
|
|
44
|
+
|| ts.isElementAccessExpression(node) && node.argumentExpression && ts.isStringLiteralLike(node.argumentExpression) && node.argumentExpression.text === 'digestText') hasDigestUse = true;
|
|
45
|
+
ts.forEachChild(node, findDigestUse);
|
|
46
|
+
}
|
|
47
|
+
findDigestUse(source);
|
|
48
|
+
function visit(node) {
|
|
49
|
+
if (ts.isTypeNode(node)) return;
|
|
50
|
+
if ((ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) && node.name) checkBinding(node.name);
|
|
51
|
+
if (ts.isIdentifier(node) && namespaces.has(node.text)
|
|
52
|
+
&& !ts.isImportClause(node.parent) && !ts.isImportSpecifier(node.parent)
|
|
53
|
+
&& !(ts.isPropertyAccessExpression(node.parent))) error(node, 'DETACHED', 'The Crypto facade cannot be detached or accessed dynamically.');
|
|
54
|
+
if (isDigest(node) && !ts.isImportSpecifier(node.parent)
|
|
55
|
+
&& !(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)
|
|
56
|
+
&& (!ts.isCallExpression(node.parent) || node.parent.expression !== node)) error(node, 'DETACHED', 'Digest operations cannot be detached or aliased.');
|
|
57
|
+
if (ts.isCallExpression(node) && isDigest(node.expression)) {
|
|
58
|
+
let fn = node.parent;
|
|
59
|
+
while (fn && !ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) fn = fn.parent;
|
|
60
|
+
const ctx = fn && fn.parameters[0] && fn.parameters[0].name;
|
|
61
|
+
if (node.arguments.length !== 2 || !ctx || !ts.isIdentifier(ctx)
|
|
62
|
+
|| !ts.isIdentifier(node.arguments[0]) || node.arguments[0].text !== ctx.text) {
|
|
63
|
+
error(node, 'ARGUMENTS', 'Digest requires the current handler context and one text input.'); return;
|
|
64
|
+
}
|
|
65
|
+
let placement = '';
|
|
66
|
+
const parent = node.parent;
|
|
67
|
+
if (ts.isAwaitExpression(parent) && ts.isVariableDeclaration(parent.parent)) placement = 'variable';
|
|
68
|
+
if (ts.isPropertyAssignment(parent) && ts.isObjectLiteralExpression(parent.parent)) {
|
|
69
|
+
const call = parent.parent.parent;
|
|
70
|
+
if (ts.isCallExpression(call) && ts.isPropertyAccessExpression(call.expression)
|
|
71
|
+
&& ts.isIdentifier(call.expression.expression) && call.expression.expression.text === ctx.text
|
|
72
|
+
&& call.expression.name.text === 'parallel' && ts.isAwaitExpression(call.parent)) placement = 'statement';
|
|
73
|
+
}
|
|
74
|
+
if (!placement) { error(node, 'PLACEMENT', 'Digest must be directly awaited or grouped.'); return; }
|
|
75
|
+
effects.push({ version: CANONICAL_PACKAGE_EFFECT_VERSION, contractId: 'pulse.crypto', package: PACKAGE, import: PACKAGE,
|
|
76
|
+
kind: 'crypto.digestText', capability: 'crypto.digestText', result: 'text-digest-result', providerKind: 'crypto', operation: 'digestText', placement,
|
|
77
|
+
range: { start: node.getStart(source), end: node.getEnd() }, resource: {}, payload: {},
|
|
78
|
+
runtimeInputs: [{ name: 'text', argumentIndex: 1, source: 'package-call-argument' }],
|
|
79
|
+
providerRequirements: ['crypto.digestText'], schemaReferences: [], redaction: ['text', 'sha256'], loc: sourceLoc(source, node) });
|
|
80
|
+
}
|
|
81
|
+
ts.forEachChild(node, visit);
|
|
82
|
+
}
|
|
83
|
+
if (hasDigestUse) visit(source);
|
|
84
|
+
const cryptoRequirements = effects.length ? [{ version: PACKAGE_CRYPTO_REQUIREMENT_VERSION, algorithms: ['SHA-256'], reachable: true, requestedBy: PACKAGE, semanticOwner: PACKAGE }] : [];
|
|
85
|
+
return { artifact: { version: 'pulse.crypto-text-lowering-plan.v1', contractId: 'pulse.crypto', package: PACKAGE, lowerableSubpath: PACKAGE,
|
|
86
|
+
status: diagnostics.length ? 'error' : 'ok', canonicalEffects: effects, cryptoRequirements },
|
|
87
|
+
canonicalEffects: effects, cryptoRequirements, diagnostics, hasErrors: diagnostics.length > 0 };
|
|
88
|
+
}
|
|
89
|
+
module.exports = { buildCryptoLoweringPlan };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
module.exports = Object.freeze({
|
|
3
|
+
version: 'pulsewasm.lowerable-library-manifest.v2', kind: 'pulsewasm.lowerable-library-manifest',
|
|
4
|
+
contractId: 'pulse.crypto', npmPackage: '@pulse-compute/crypto', lowerableSubpath: '@pulse-compute/crypto',
|
|
5
|
+
facade: { namespace: 'crypto', import: '@pulse-compute/crypto', symbols: ['digestText'] },
|
|
6
|
+
publicApi: { package: '@pulse-compute/crypto', symbols: ['crypto', 'digestText'], compatibilitySubpaths: [], loweringIntentionallyNarrow: true },
|
|
7
|
+
compiler: { version: 'pulsewasm.lowerable-compiler-builder.v1', entry: './pulsewasm.compiler.cjs', export: 'buildCryptoLoweringPlan', artifact: 'crypto-lowering-plan.json', builderOwner: '@pulse-compute/crypto', trust: 'first-party', inputs: ['typescript.SourceFile'] },
|
|
8
|
+
modes: {
|
|
9
|
+
typescript: { entry: './dist/index.js' }, jsEngine: { entry: './dist/index.js', behavior: 'provider-dependent' },
|
|
10
|
+
wasm: { mode: 'wasm-sidecar', sidecar: './as/digest-text.as.ts',
|
|
11
|
+
lowerings: [{ tsSymbol: 'crypto.digestText', asSymbol: '__pulse_crypto_digest_text', callShape: 'context-runtime-text', hostCapabilities: ['result'] }],
|
|
12
|
+
hostCapabilities: ['result'] }
|
|
13
|
+
},
|
|
14
|
+
policy: { packageOwnsFacadeMapping: true, canonicalRootLowering: true, automaticFallback: false }
|
|
15
|
+
});
|
package/pulsewasm.native.cjs
CHANGED
|
@@ -45,6 +45,7 @@ function pulseHmacAssemblyScriptSource(algorithm = 'HS256') {
|
|
|
45
45
|
'pulse_crypto_hs256_verify',
|
|
46
46
|
'pulse_crypto_sha256_digest',
|
|
47
47
|
'pulse_crypto_bytes_frame_v1',
|
|
48
|
+
'pulse_crypto_digest_frame_v1',
|
|
48
49
|
'pulse_crypto_sha256_bytes_v1',
|
|
49
50
|
'pulse_crypto_hmac_sha256_bytes_v1'
|
|
50
51
|
]),
|
|
@@ -55,8 +56,10 @@ function pulseHmacAssemblyScriptSource(algorithm = 'HS256') {
|
|
|
55
56
|
hs256TagBytes: 32,
|
|
56
57
|
byteOperationKeyBytesMaximum: 8192,
|
|
57
58
|
byteOperationDataBytesMaximum: 32768,
|
|
59
|
+
digestDataBytesMaximum: 2097152,
|
|
58
60
|
byteOperationOutputBytes: 32,
|
|
59
|
-
byteOperationFrameBytes: 40992
|
|
61
|
+
byteOperationFrameBytes: 40992,
|
|
62
|
+
digestFrameBytes: 2097184
|
|
60
63
|
}),
|
|
61
64
|
resultCodes: Object.freeze({
|
|
62
65
|
valid: 1,
|
|
@@ -76,20 +79,23 @@ function pulseHmacAssemblyScriptSource(algorithm = 'HS256') {
|
|
|
76
79
|
|
|
77
80
|
function bindNativeDigestMac(moduleExports) {
|
|
78
81
|
const memory = moduleExports && moduleExports.memory;
|
|
79
|
-
const names = ['pulse_crypto_bytes_frame_v1', 'pulse_crypto_sha256_bytes_v1', 'pulse_crypto_hmac_sha256_bytes_v1'];
|
|
82
|
+
const names = ['pulse_crypto_bytes_frame_v1', 'pulse_crypto_digest_frame_v1', 'pulse_crypto_sha256_bytes_v1', 'pulse_crypto_hmac_sha256_bytes_v1'];
|
|
80
83
|
if (!(memory instanceof WebAssembly.Memory) || names.some((name) => typeof moduleExports[name] !== 'function')) {
|
|
81
84
|
throw new TypeError('Native Crypto digest/MAC exports are unavailable.');
|
|
82
85
|
}
|
|
83
|
-
const frameBytes = 8192 + 32768 + 32;
|
|
84
86
|
function run(data, key) {
|
|
85
|
-
if (!(data instanceof Uint8Array) || data.length > 32768
|
|
87
|
+
if (!(data instanceof Uint8Array) || data.length > (key === undefined ? 2097152 : 32768)
|
|
86
88
|
|| (key !== undefined && (!(key instanceof Uint8Array) || key.length > 8192))) {
|
|
87
89
|
throw new TypeError('Native Crypto digest/MAC input exceeds its byte contract.');
|
|
88
90
|
}
|
|
89
|
-
const
|
|
91
|
+
const largeDigest = key === undefined && data.length > 32768;
|
|
92
|
+
const dataBytes = largeDigest ? 2097152 : 32768;
|
|
93
|
+
const keyBytes = largeDigest ? 0 : 8192;
|
|
94
|
+
const frameBytes = keyBytes + dataBytes + 32;
|
|
95
|
+
const pointer = (largeDigest ? moduleExports.pulse_crypto_digest_frame_v1() : moduleExports.pulse_crypto_bytes_frame_v1()) >>> 0;
|
|
90
96
|
if (pointer + frameBytes > memory.buffer.byteLength) throw new TypeError('Native Crypto frame is outside memory.');
|
|
91
|
-
const dataPointer = pointer +
|
|
92
|
-
const outputPointer = dataPointer +
|
|
97
|
+
const dataPointer = pointer + keyBytes;
|
|
98
|
+
const outputPointer = dataPointer + dataBytes;
|
|
93
99
|
try {
|
|
94
100
|
const view = new Uint8Array(memory.buffer);
|
|
95
101
|
view.set(data, dataPointer);
|
|
@@ -118,7 +124,73 @@ function pulseEs256GuestUnit() {
|
|
|
118
124
|
});
|
|
119
125
|
}
|
|
120
126
|
|
|
127
|
+
|
|
128
|
+
function bindNativeEs256Signer(moduleExports) {
|
|
129
|
+
const memory = moduleExports && moduleExports.memory;
|
|
130
|
+
const sign = moduleExports && moduleExports.pulse_crypto_es256_sign;
|
|
131
|
+
if (!(memory instanceof WebAssembly.Memory) || memory.buffer.byteLength !== 2097152 || typeof sign !== 'function') {
|
|
132
|
+
throw new TypeError('Native ES256 signing exports are unavailable.');
|
|
133
|
+
}
|
|
134
|
+
return (key, data) => {
|
|
135
|
+
if (!(key instanceof Uint8Array) || key.length !== 96 || !(data instanceof Uint8Array) || data.length > 16340) {
|
|
136
|
+
throw new TypeError('Invalid ES256 signing byte request.');
|
|
137
|
+
}
|
|
138
|
+
const pointer = 524288, capacity = 16640;
|
|
139
|
+
const frame = new Uint8Array(memory.buffer, pointer, capacity);
|
|
140
|
+
frame.fill(0);
|
|
141
|
+
try {
|
|
142
|
+
const header = new DataView(memory.buffer, pointer, 64);
|
|
143
|
+
for (const [offset, value] of [[0, 0x32534550], [4, 2], [8, 64], [12, (224 + data.length + 15) & ~15],
|
|
144
|
+
[16, 1], [24, 224], [28, data.length], [32, 64], [36, 96], [40, 160], [44, 64]]) header.setUint32(offset, value, true);
|
|
145
|
+
frame.set(key, 64); frame.set(data, 224);
|
|
146
|
+
const status = sign(pointer, capacity);
|
|
147
|
+
if (status === -1) throw Object.assign(new TypeError('Invalid P-256 signing key.'), { code: 'PULSE_CRYPTO_KEY_INVALID' });
|
|
148
|
+
if (status !== 1) throw new TypeError('Native ES256 signing failed.');
|
|
149
|
+
return frame.slice(160, 224);
|
|
150
|
+
} finally {
|
|
151
|
+
frame.fill(0);
|
|
152
|
+
// The synchronous guest has returned: clear its borrowed stack as well.
|
|
153
|
+
new Uint8Array(memory.buffer, 0, 65536).fill(0);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function bindNativeRs256(moduleExports) {
|
|
159
|
+
const memory = moduleExports && moduleExports.memory;
|
|
160
|
+
if (!(memory instanceof WebAssembly.Memory) || memory.buffer.byteLength !== 2097152
|
|
161
|
+
|| typeof moduleExports.pulse_crypto_rs256_sign !== 'function' || typeof moduleExports.pulse_crypto_rs256_verify !== 'function') {
|
|
162
|
+
throw new TypeError('Native RS256 exports are unavailable.');
|
|
163
|
+
}
|
|
164
|
+
const invalidKey = () => Object.assign(new TypeError('Invalid RSA signing key.'), { code: 'PULSE_CRYPTO_KEY_INVALID' });
|
|
165
|
+
function run(key, data, signature) {
|
|
166
|
+
const signing = signature === undefined;
|
|
167
|
+
if (!(key instanceof Uint8Array) || key.length < 8) throw invalidKey();
|
|
168
|
+
const k = new DataView(key.buffer, key.byteOffset, 4).getUint32(0, true);
|
|
169
|
+
if (![256, 384, 512].includes(k) || key.length !== (signing ? 8 + k * 9 / 2 : 8 + k)) throw invalidKey();
|
|
170
|
+
if (!(data instanceof Uint8Array) || data.length > 12288
|
|
171
|
+
|| !signing && (!(signature instanceof Uint8Array) || signature.length !== k)) throw new TypeError('Invalid RS256 byte request.');
|
|
172
|
+
const pointer = 524288, capacity = 16640, output = (64 + key.length + 15) & ~15, input = output + k;
|
|
173
|
+
const frame = new Uint8Array(memory.buffer, pointer, capacity);
|
|
174
|
+
frame.fill(0);
|
|
175
|
+
try {
|
|
176
|
+
const header = new DataView(memory.buffer, pointer, 64);
|
|
177
|
+
for (const [offset, value] of [[0, 0x32534550], [4, 2], [8, 64], [12, (input + data.length + 15) & ~15],
|
|
178
|
+
[16, 2], [24, input], [28, data.length], [32, 64], [36, key.length], [40, output], [44, k]]) header.setUint32(offset, value, true);
|
|
179
|
+
frame.set(key, 64); frame.set(data, input);
|
|
180
|
+
if (!signing) frame.set(signature, output);
|
|
181
|
+
const status = moduleExports[signing ? 'pulse_crypto_rs256_sign' : 'pulse_crypto_rs256_verify'](pointer, capacity);
|
|
182
|
+
if (!signing) return Object.freeze({ status: ({ 1: 'valid', 0: 'invalid-authenticator', '-1': 'invalid-key', '-2': 'invalid-input' })[status] || 'realization-failure' });
|
|
183
|
+
if (status === -1) throw invalidKey();
|
|
184
|
+
if (status !== 1) throw new TypeError('Native RS256 signing failed.');
|
|
185
|
+
return frame.slice(output, output + k);
|
|
186
|
+
} finally { frame.fill(0); new Uint8Array(memory.buffer, 0, 65536).fill(0); }
|
|
187
|
+
}
|
|
188
|
+
return Object.freeze({ sign: (key, data) => run(key, data), verify: (key, data, signature) => run(key, data, signature) });
|
|
189
|
+
}
|
|
190
|
+
|
|
121
191
|
module.exports = Object.freeze({
|
|
192
|
+
bindNativeEs256Signer,
|
|
193
|
+
bindNativeRs256,
|
|
122
194
|
CRYPTO_GUEST_SOURCE_CONTRACT_VERSION,
|
|
123
195
|
GUEST_UNIT_CONTRIBUTION_VERSION,
|
|
124
196
|
pulseHmacAssemblyScriptSource,
|