facebook / akd

An implementation of an auditable key directory

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Compiling to WebAssembly targets

chris-wood opened this issue · comments

akd seems to depend upon tokio for some basic functionality like time and read-write locks, as well as test support. This is all well and fine, but unfortunately tokio has a dependency on mio, which does not compile for Wasm targets. Is it possible to support compilation to wasm targets? That would allow this code to be run in browser and related runtimes.

I think mainly the server-side functionality is what needs the tokio dependency for async functions. However, the client-side functionality (and audit verification functionality) should be able to be run without tokio, and should hopefully be wasm-compatible.

At the moment, it's not organized like this, but thanks for bringing this up! We can certainly use this issue to track progress on wasm support for proof verification operations.

@chris-wood: Is this what you meant by compiling to WebAssembly targets? Or were you also looking for compatibility for server-side functionality?

Yeah, I'm thinking about the client-side functionality, which includes audit verification. I'm having trouble compiling to wasm. It's very possible I'm holding it wrong. What features would one need to include to implement auditing functionality? At a high level, I would expect that to be public_auditing and serde_serialization.

Edit: Ah, sorry, I misread. You're saying that it's possible to compile to wasm, but currently not configured to work that way.

Another option you can take a look into is using the akd_client repo instead, which does support wasm, but only provides client functionality (not verification of audit proofs). Actually, it's likely that we will keep the main akd repo to have the tokio dependency and not support wasm, whereas the akd_client repo will be where all of the wasm-compatible code lives. Sorry, I think my original answer did not factor in the existing wasm support in akd_client, which might have caused some confusion.

Now, akd_client doesn't support the audit_verify operation at the moment, but perhaps this could be added in. @slawlor heads up on that

Open to PRs on the client repo to add audit_verify? =)

I think that should be fine if it is gated behind a feature!

Great! What do you think about moving the audit code inside akd into an akd_audit module, similar to what's done for akd_client, akd_mysql, etc?

Sounds like a good idea. Thanks for the suggestion!

Yeah I think to match web assembly we'll need to look at probably another database implementation which doesn't use futures (or at least doesn't use Tokio). Let's wait until #269 is merged before tackling this, but I think it should be straightforward enough. We could probably also refactor out the usage of tokio::sync::RwLock for another lock implementation and perhaps remove Tokio all-together as a dependency, but that's probably a bigger lift.

Having async's should be fine, it's going to be around leaning out the dependencies as much as possible to get to a leaner client (dependency-wise).

Also, just as a note, akd_mysql is an extension of akd by giving MySQL support, while akd_client is a companion to akd and doesn't have a direct dependency (unless you need to convert akd types to the protobuf types or something). Ideally akd_client should be stand-alone and just take in protobuf specified binary blobs and be able to do the verification on its own.

So I took a small stab at assessing this change and there's a few things to note.

  1. The reason we didn't port the auditor originally is because it makes use of all of the primitives of akd (essentially). The Azks, TreeNode, and all the insertion + hashing logic.
  2. Because of the degree of type dependency in (1) with the directory, I'm not thrilled about the idea of duplicating so much code across 2 crates since we'll have to manually keep it in sync. That leads me towards preferring that we make a common crate, perhaps without winter_crypto, tokio, etc say perhaps called akd_primitives or something like that. This would be somehow decoupling storage (which is the bulk of the tokio dependencies) out from the core akd logic. I'm not exactly sure how possible that's going to be.

Overall I think we're probably better off leaning down akd, like we did with akd_client and try and remove tokio specific primitives (sleep + RwLock). But open to any thoughts!

That sounds like a great idea! But it's easier said than done though. I'm not familiar enough with the library to know what are the core components you'd move to akd_primitives. Do you have a list off the top of your head?

I'm not even sure how possible it would be to build a "primitives" crate, aside from just the types. The problem is the storage module is very tightly coupled with the logic to build and manage the directory. I'm working on removing the dependency on tokio at the moment, and then perhaps the winter_* stuff which might be enough to compile to a WASM target.

Thanks for taking a look at this @slawlor. I think specifically for the winter_* stuff, we can disable reliance on std by passing default-features = false, which should be enough for compiling to WASM. But long-term I agree it would be simpler to remove the winter_* dependencies anyway.

Ok so after spending most of a day playing around with this, it seems we can't fully remove Tokio due to RwLock in the std crate not being safe for use across multiple threads. I hacked around it, but using unsafe blocks to implement Sync and Send I think leaves us open to hidden potential deadlocks.

Tokio's implementation of RwLock is multi-thread safe, so really it's kind of the only option.

That being said, we're only really using 2 features of Tokio, "sync" and "time" which don't pull in ANY dependencies except for

pin-project-lite = "0.2.0"

I think. It's a little hard to read, because tokio has a lot of environment-conditional compilation, but it definitely removes the dependence on mio. tokio's Cargo.toml

All that being said, I was able to refactor out our uses's of RwLock to a common one I wrote up in the utils module. But if we're just going to standardize on Tokio's RwLock I think I'm going to revert that and come up with a minimal PR to lean out the tokio dependencies.

OK both of these PRs I think lean-out what's necessary in akd to compile to WASM. If we hit anything else, let me know and I'll take another look at further dependency removals.

@slawlor Perhaps it's worth also adding the building to the wasm32-unknown-unknown target into our CI, so we can confirm that wasm compilation would indeed be successful for these libraries?

Example: https://github.com/novifinancial/opaque-ke/blob/main/.github/workflows/main.yml#L126-L134

Nice work, @slawlor =) And great idea, @kevinlewi!

@kevinlewi uhm...

$ cargo build --target=wasm32-unknown-unknown --features protobuf,blake3 --no-default-features -p akd
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
package:   /Users/seanlawlor/Code/SEEMless/akd_core/Cargo.toml
workspace: /Users/seanlawlor/Code/SEEMless/Cargo.toml
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
package:   /Users/seanlawlor/Code/SEEMless/akd_client/Cargo.toml
workspace: /Users/seanlawlor/Code/SEEMless/Cargo.toml
   Compiling cfg-if v1.0.0
   Compiling typenum v1.15.0
   Compiling getrandom v0.1.16
   Compiling generic-array v0.14.6
   Compiling serde v1.0.144
   Compiling ppv-lite86 v0.2.16
   Compiling once_cell v1.13.1
   Compiling hashbrown v0.12.3
   Compiling signature v1.6.0
   Compiling opaque-debug v0.3.0
   Compiling thiserror v1.0.33
   Compiling subtle v2.4.1
   Compiling byteorder v1.4.3
   Compiling zeroize v1.5.7
   Compiling parking_lot_core v0.9.3
   Compiling blake3 v1.3.1
   Compiling akd_core v0.8.0 (/Users/seanlawlor/Code/SEEMless/akd_core)
   Compiling lock_api v0.4.8
   Compiling indexmap v1.9.1
   Compiling value-bag v1.0.0-alpha.9
   Compiling rand_core v0.5.1
   Compiling protobuf-support v3.2.0
   Compiling protobuf v3.2.0
   Compiling tokio v1.20.1
   Compiling constant_time_eq v0.1.5
   Compiling arrayref v0.3.6
   Compiling smallvec v1.9.0
   Compiling rand_chacha v0.2.2
   Compiling scopeguard v1.1.0
   Compiling arrayvec v0.7.2
   Compiling log v0.4.17
   Compiling rand v0.7.3
   Compiling pin-project-lite v0.2.9
   Compiling atty v0.2.14
   Compiling hex v0.4.3
   Compiling lazy_static v1.4.0
   Compiling dashmap v5.4.0
   Compiling colored v2.0.0
   Compiling keyed_priority_queue v0.3.2
   Compiling digest v0.9.0
   Compiling block-buffer v0.9.0
   Compiling sha2 v0.9.9
   Compiling curve25519-dalek v3.2.0
   Compiling ed25519 v1.5.2
   Compiling serde_bytes v0.11.7
   Compiling bincode v1.3.3
   Compiling ed25519-dalek v1.0.1
   Compiling akd v0.8.0 (/Users/seanlawlor/Code/SEEMless/akd)
    Finished dev [unoptimized + debuginfo] target(s) in 13.08s

it worked? We could add a CI step, but it seems that it compiled to the target just fine as an rlib

A wild stab in the dark, but it does indeed compile to a WASM binary and expose the proper js output

51c0eca

  1. This depends on #278
  2. The output js spec looks like
/**
* Verify a single audit proof for epoch to epoch+1
* @param {Uint8Array} current_hash
* @param {Uint8Array} previous_hash
* @param {Uint8Array} single_proof_ref
* @param {bigint} epoch
* @returns {Promise<any>}
*/
export function single_audit_verify(current_hash, previous_hash, single_proof_ref, epoch) {
    try {
        var ptr0 = passArray8ToWasm0(current_hash, wasm.__wbindgen_malloc);
        var len0 = WASM_VECTOR_LEN;
        var ptr1 = passArray8ToWasm0(previous_hash, wasm.__wbindgen_malloc);
        var len1 = WASM_VECTOR_LEN;
        var ptr2 = passArray8ToWasm0(single_proof_ref, wasm.__wbindgen_malloc);
        var len2 = WASM_VECTOR_LEN;
        uint64CvtShim[0] = epoch;
        const low3 = u32CvtShim[0];
        const high3 = u32CvtShim[1];
        const ret = wasm.single_audit_verify(ptr0, len0, ptr1, len1, ptr2, len2, low3, high3);
        return takeObject(ret);
    } finally {
        current_hash.set(getUint8Memory0().subarray(ptr0 / 1, ptr0 / 1 + len0));
        wasm.__wbindgen_free(ptr0, len0 * 1);
        previous_hash.set(getUint8Memory0().subarray(ptr1 / 1, ptr1 / 1 + len1));
        wasm.__wbindgen_free(ptr1, len1 * 1);
        single_proof_ref.set(getUint8Memory0().subarray(ptr2 / 1, ptr2 / 1 + len2));
        wasm.__wbindgen_free(ptr2, len2 * 1);
    }
}

Are we good to go with closing this issue? I believe wasm compilation now works properly. @chris-wood

Yep, let's close this. Thanks for the work!