hyperledger / cacti

Hyperledger Cacti is a new approach to the blockchain interoperability problem
https://wiki.hyperledger.org/display/cactus
Apache License 2.0
338 stars 278 forks source link

feat(common): use ursa JS/WASM #296

Closed petermetz closed 1 year ago

petermetz commented 3 years ago

Is your feature request related to a problem? Please describe.

Would be nice not having to roll our own crytpo, even if it's mostly just re-using existing libraries. Ursa was invented for this reason, but it's written in rust.

Describe the solution you'd like

We should use the ursa JS/WASM build (which does not exist yet) as described here: https://github.com/hyperledger/ursa/issues/150

Describe alternatives you've considered

I considered a Node native addon that could use the machine code binaries of ursa that are being built today, but would not be cross-platform (browser) and also I'm trying to avoid Node native addons like the plague.

Additional context

This is a very forward looking issue as we do not yet have an ursa JS/WASM build that we could use. Follow this for progress on that: https://github.com/hyperledger/ursa/issues/150

hartm commented 3 years ago

It might be best if we did this in multiple stages.

The first thing to do would be to define interfaces for the cryptographic protocols we need. These should be compatible or easy to use with Ursa.

Then, we can build this interface. We can build a "dummy" interface that just has very simple calls, but still replace the crypto calls in our code with generic calls. This would maintain functionality, as we would still be using the same crypto calls we currently use, just with an extra level of abstraction.

Finally, we can switch to the Ursa JS/WASM build. This will require a PR on Ursa (from someone--we can probably get Ursa people to help). But this will allow us to pick from the Ursa crypto primitives, and, if we are smart about how we build things, allow people to, say, verify others' signatures even if they are using a different scheme. Once we have a generic interface in our code, this should be much easier.

What do you all think?

petermetz commented 3 years ago

Edit: adding missing tag: @hartm

It might be best if we did this in multiple stages.

Agreed, my plan is to start by just working with signature generation/verification related code (that's what we have immediate use for on Cactus at the moment).

The first thing to do would be to define interfaces for the cryptographic protocols we need. These should be compatible or easy to use with Ursa.

Short answer of mine to that: everything.

What I imagined is a build process that provides 100% the same library API surface on JS as it is on Rust. I don't want to have to separately document the JS build on account of it having a different API surface, nor do I want people to be disappointed when they find out that the one method/class/whatever they needed from Ursa that was their favorite feature is actually the one that's missing from the JS build for whatever reason.

The final JS build should be able to do 100% of the things that the Rust build can for a person who is working on a Rust project. So my answer to the "what cryptographic protocols we need" question would be: Everything that ursa has or will ever have, we need it and preferably the JS build would be able to just automatically pick it up and include it through some one-liner import statement or not even that. Ideally the build would just take care of it so that once something is part of Ursa Rust, it's also part of Ursa JS and it's just a matter of pushing the new release to npm as a package. I know this sounds like the "write once, run anywhere" pie in the sky dream that never happens in the end, but I want to try and shoot for the stars. :-)

hartm commented 3 years ago

Thanks for the clarifications!

I don't think we need to start with everything in Ursa. In particular, we don't use any zero knowledge stuff in Cactus right now and probably won't for a reasonable time, so it's safe to not start with that.

We also use cryptographic hashes in the current codebase, so we would want an abstraction for that as well.

I think a good first step is to come up with a JS interface for modular signatures and hashes that we are happy with, possibly copying from the Ursa interfaces. Rust probably makes things a little bit easier than Javascript here, but we can work around it.

The only tricky thing is to decide how we want to indicate which particular, say, signature algorithm we are using. I don't know exactly what the optimal way to do this in JS would be.

Tagging @petermetz .

petermetz commented 3 years ago

@hartm

Okay, I needed to go through a quick Rust 101 first, but now it makes sense why we can't just magically include everything without having to manually define the interfaces for JS. All agreed.

I think a good first step is to come up with a JS interface for modular signatures and hashes that we are happy with, possibly copying from the Ursa interfaces. Rust probably makes things a little bit easier than Javascript here, but we can work around it.

In that case, what do you think of starting with these:

I'm still not a 100% on reading the rust code, so do let me know if this matches what you were thinking or if it makes no sense... :-) For example I'm not sure where the file ./libursa/src/hash/mod.rs gets the source code to it's #[cfg(feature = "sha3")] pub use sha3; export statement (but I also don't want this thread to turn into a rust tutorial for me so I'll just keep expanding my understanding piece by piece)

hartm commented 3 years ago

Is there a reason you want to use secp256k1 instead of the generic signature interface?

But yes, informally, Rust uses traits for abstraction. You can see here what the generic signature scheme looks like with traits: https://github.com/hyperledger/ursa/blob/master/libursa/src/signatures/mod.rs. You can see how we define a generic signature scheme in Ursa:

pub trait SignatureScheme { fn new() -> Self; fn keypair( &self, options: Option, ) -> Result<(PublicKey, PrivateKey), CryptoError>; fn sign(&self, message: &[u8], sk: &PrivateKey) -> Result<Vec, CryptoError>; fn verify(&self, message: &[u8], signature: &[u8], pk: &PublicKey) -> Result<bool, CryptoError>; fn signature_size() -> usize; fn private_key_size() -> usize; fn public_key_size() -> usize; }

It has (essentially) a constructor, KeyGen(), Sign(), and Verify() algorithms, and parameters that indicate the size of everything.

We will want some similar way of expressing this generically in Javascript. Ideally our code should only call on generic functions like these so that we can attain maximum modularity. Rust makes it easy to instantiate and use this interface with many different signature schemes. I'll leave it to you to figure out how we can best to this in Javascript. ;)

@petermetz

petermetz commented 3 years ago

Is there a reason you want to use secp256k1 instead of the generic signature interface?

Yes, blissful unawareness of the generic signature interface on my part. :-) Agreed that we should use the generic one!

We will want some similar way of expressing this generically in Javascript.

Agreed, this part I can definitely take care of myself and I'm confident it will work because JS does not have a strong type system so that affords a lot of flexibility (which can be a bane, but is a boon in this case).

Ideally our code should only call on generic functions like these so that we can attain maximum modularity.

If I were to pull in Ursa as a crate for my imaginary rust project, would I be able to directly access the specific implementations such as secp256k1 or only the generic trait (that indirectly calls into the specific secp256k1 implementation)? In other words, is the callable API surface of Ursa restricted to these top level, generic traits? Sorry if this is just another basic rust question!

@hartm

hartm commented 3 years ago

In order to do this, we will need a javascript wrapper for Ursa. This would go in an Ursa directory, most likely (Ursa maintainers have accepted a similar thing for go). At that point, we could create a "Cactus config" crate from Ursa that is basically as customized as we want and pulls in only what we use.

So I guess the answer to your question is yes, but we will need to PR Ursa to do it.

That being said, we'd still want to use the generic interface. Is there any particular reason you'd want to be able to access the specific implementation itself directly? But we could only put the crypto code we actually use in the crate, so there wouldn't be bloat. Does this make sense?

@petermetz

petermetz commented 3 years ago

@hartm All understood!

Is there any particular reason you'd want to be able to access the specific implementation itself directly?

I was just curious if the pure rust build makes it possible or not, but most likely will not be able to spend extra time on making the same thing possible in the JS build to begin with (trying to reduce effort just so that we can get the ball rolling ASAP and work with incremental improvements later as we go).

RafaelAPB commented 2 years ago

Any updates on this?

petermetz commented 1 year ago

Any updates on this?

@RafaelAPB Not yet unfortunately. In the meantime ursa got EOL'd [1] so I'm closing this due to that as well.

[1] https://wiki.hyperledger.org/display/ursa