function defineWasmEnv(label) { label += ': '; let AB_HEAP; let ATU8_HEAP; let ATU32_HEAP; // eslint-disable-next-line no-console const console_out = (s_channel, s_out) => console[s_channel](label + s_out.replace(/\0/g, '\n')); let s_error = ''; // for converting bytes to text const utf8 = new TextDecoder(); const h_fds = { // stdout 1(s_out) { console_out('debug', s_out); }, // stderr 2(s_out) { console_out('error', (s_error = s_out)); } }; const imports = { abort() { throw Error(label + (s_error || 'An unknown error occurred')); }, memcpy: (ip_dst, ip_src, nb_size) => ATU8_HEAP.copyWithin(ip_dst, ip_src, ip_src + nb_size), resize(w) { throw Error(label + `Out of memory (resizing ${w})`); }, write(i_fd, ip_iov, nl_iovs, ip_written) { // output string let s_out = ''; // track number of bytes read from buffers let cb_read = 0; // each pending iov for (let i_iov = 0; i_iov < nl_iovs; i_iov++) { // start of buffer in memory const ip_start = ATU32_HEAP[ip_iov >> 2]; // size of buffer const nb_len = ATU32_HEAP[(ip_iov + 4) >> 2]; ip_iov += 8; // extract text from buffer s_out += utf8.decode(ATU8_HEAP.subarray(ip_start, ip_start + nb_len)); // update number of bytes read cb_read += nb_len; } // route to fd if (h_fds[i_fd]) { h_fds[i_fd](s_out); } else { // no fd found throw new Error(`libsecp256k1 tried writing to non-open file descriptor: ${i_fd}\n${s_out}`); } // write bytes read ATU32_HEAP[ip_written >> 2] = cb_read; // no error return 0; } }; return [ imports, (d_memory) => [ (AB_HEAP = d_memory.buffer), (ATU8_HEAP = new Uint8Array(AB_HEAP)), (ATU32_HEAP = new Uint32Array(AB_HEAP)) ] ]; } /* * ================================ * GENERATED FILE WARNING * Do not edit this file manually. * ================================ */ const map_wasm_imports = (g_imports) => ({ a: { a: g_imports.abort, f: g_imports.memcpy, d: g_imports.resize, e: () => 52, // _fd_close, c: () => 70, // _fd_seek, b: g_imports.write, }, }); const map_wasm_exports = (g_exports) => ({ malloc: g_exports['i'], free: g_exports['j'], sha256_initialize: g_exports['l'], sha256_write: g_exports['m'], sha256_finalize: g_exports['n'], context_create: g_exports['o'], xonly_pubkey_parse: g_exports['p'], xonly_pubkey_serialize: g_exports['q'], keypair_create: g_exports['r'], keypair_xonly_pub: g_exports['s'], schnorrsig_sign32: g_exports['t'], schnorrsig_verify: g_exports['u'], sbrk: g_exports['sbrk'], memory: g_exports['g'], init: () => g_exports['h'](), }); /** * Creates a new instance of the secp256k1 WASM and returns the Nostr wrapper * @param z_src - a Response containing the WASM binary, a Promise that resolves to one, * or the raw bytes to the WASM binary as a {@link BufferSource} * @returns the wrapper API */ const NostrWasm = async (z_src) => { // prepare the runtime const [defs, f_bind_heap] = defineWasmEnv('nostr-wasm'); const g_imports = map_wasm_imports(defs); // prep the wasm module let d_wasm; // instantiate wasm binary by streaming the response bytes if (z_src instanceof Response || z_src instanceof Promise) { d_wasm = await WebAssembly.instantiateStreaming(z_src, g_imports); } else { // instantiate using raw binary d_wasm = await WebAssembly.instantiate(z_src, g_imports); } // create the exports struct const g_wasm = map_wasm_exports(d_wasm.instance.exports); // bind the heap and ref its view(s) const [, ATU8_HEAP] = f_bind_heap(g_wasm.memory); // call into the wasm module's init method g_wasm.init(); const ip_sk = g_wasm.malloc(32 /* ByteLens.PRIVATE_KEY */); const ip_ent = g_wasm.malloc(32 /* ByteLens.NONCE_ENTROPY */); const ip_msg_hash = g_wasm.malloc(32 /* ByteLens.MSG_HASH */); // scratch spaces const ip_pubkey_scratch = g_wasm.malloc(32 /* ByteLens.XONLY_PUBKEY */); const ip_sig_scratch = g_wasm.malloc(64 /* ByteLens.BIP340_SIG */); // library handle: secp256k1_keypair; const ip_keypair = g_wasm.malloc(96 /* ByteLens.KEYPAIR_LIB */); // library handle: secp256k1_xonly_pubkey; const ip_xonly_pubkey = g_wasm.malloc(64 /* ByteLens.XONLY_KEY_LIB */); // library handle: secp256k1_sha256; const ip_sha256 = g_wasm.malloc(104 /* ByteLens.SHA256_LIB */); // create a reusable context const ip_ctx = g_wasm.context_create(513 /* Flags.CONTEXT_SIGN */ | 257 /* Flags.CONTEXT_VERIFY */); // an encoder for hashing strings const utf8 = new TextEncoder(); /** * Puts the given private key into program memory, runs the given callback, then zeroes out the key * @param atu8_sk - the private key * @param f_use - callback to use the key * @returns whatever the callback returns */ const with_keypair = (atu8_sk, f_use) => { // prep callback return let w_return; // in case of any exception.. try { // copy input bytes into place ATU8_HEAP.set(atu8_sk, ip_sk); // instantiate keypair g_wasm.keypair_create(ip_ctx, ip_keypair, ip_sk); // use private key w_return = f_use(); } finally { // zero-out private key and keypair ATU8_HEAP.fill(1, ip_sk, ip_sk + 32 /* ByteLens.PRIVATE_KEY */); ATU8_HEAP.fill(2, ip_keypair, ip_keypair + 96 /* ByteLens.KEYPAIR_LIB */); } // forward result return w_return; }; const compute_event_id = (event) => { const message = utf8.encode(`[0,"${event.pubkey}",${event.created_at},${event.kind},${JSON.stringify(event.tags)},${JSON.stringify(event.content)}]`); const ip_message = g_wasm.malloc(message.length); ATU8_HEAP.set(message, ip_message); g_wasm.sha256_initialize(ip_sha256); g_wasm.sha256_write(ip_sha256, ip_message, message.length); g_wasm.sha256_finalize(ip_sha256, ip_msg_hash); g_wasm.free(ip_message); return ATU8_HEAP.slice(ip_msg_hash, ip_msg_hash + 32 /* ByteLens.MSG_HASH */); }; return { generateSecretKey: () => crypto.getRandomValues(new Uint8Array(32 /* ByteLens.PRIVATE_KEY */)), getPublicKey(sk) { if (1 /* BinaryResult.SUCCESS */ !== with_keypair(sk, () => g_wasm.keypair_xonly_pub(ip_ctx, ip_xonly_pubkey, null, ip_keypair))) { throw Error('failed to get pubkey from keypair'); } // serialize the public key g_wasm.xonly_pubkey_serialize(ip_ctx, ip_pubkey_scratch, ip_xonly_pubkey); // extract result return ATU8_HEAP.slice(ip_pubkey_scratch, ip_pubkey_scratch + 32 /* ByteLens.XONLY_PUBKEY */); }, finalizeEvent(event, seckey, ent) { with_keypair(seckey, () => { // get public key (as in getPublicKey function above) g_wasm.keypair_xonly_pub(ip_ctx, ip_xonly_pubkey, null, ip_keypair); g_wasm.xonly_pubkey_serialize(ip_ctx, ip_pubkey_scratch, ip_xonly_pubkey); const pubkey = ATU8_HEAP.slice(ip_pubkey_scratch, ip_pubkey_scratch + 32 /* ByteLens.XONLY_PUBKEY */); event.pubkey = toHex(pubkey); // compute event id event.id = toHex(compute_event_id(event)); // copy entropy bytes into place, if they are provided if (!ent && crypto.getRandomValues) { ATU8_HEAP.set(crypto.getRandomValues(new Uint8Array(32)), ip_ent); } // perform signature (ip_msg_hash is already set from procedure above) if (1 /* BinaryResult.SUCCESS */ !== g_wasm.schnorrsig_sign32(ip_ctx, ip_sig_scratch, ip_msg_hash, ip_keypair, ip_ent)) { throw Error('failed to sign'); } }); const sig = ATU8_HEAP.slice(ip_sig_scratch, ip_sig_scratch + 64 /* ByteLens.BIP340_SIG */); event.sig = toHex(sig); }, verifyEvent(event) { const id = fromHex(event.id); // check event hash const computed = compute_event_id(event); for (let i = 0; i < id.length; i++) { if (id[i] !== computed[i]) throw Error('id is invalid'); } // copy event data into place ATU8_HEAP.set(fromHex(event.sig), ip_sig_scratch); ATU8_HEAP.set(fromHex(event.id), ip_msg_hash); ATU8_HEAP.set(fromHex(event.pubkey), ip_pubkey_scratch); // parse the public key if (1 /* BinaryResult.SUCCESS */ !== g_wasm.xonly_pubkey_parse(ip_ctx, ip_xonly_pubkey, ip_pubkey_scratch)) { throw Error('pubkey is invalid'); } // verify the signature if (1 /* BinaryResult.SUCCESS */ !== g_wasm.schnorrsig_verify(ip_ctx, ip_sig_scratch, ip_msg_hash, 32 /* ByteLens.MSG_HASH */, ip_xonly_pubkey)) { throw Error('signature is invalid'); } } }; }; function toHex(bytes) { return bytes.reduce((hex, byte) => hex + byte.toString(16).padStart(2, '0'), ''); } function fromHex(hex) { return new Uint8Array(hex.length / 2).map((_, i) => parseInt(hex.slice(i * 2, i * 2 + 2), 16)); } export { NostrWasm as N };