Open oleganza opened 6 years ago
This looks great! I think once there's an example up it should be fairly straightforward for someone to adapt to their language of choice. I was originally skeptical about all the bindings being in one repo but thinking about it more it makes sense, given the applications of this library, having feedback on a binding would very helpful.
One question that I'm not clear on yet is where exactly to draw an FFI boundary. It might turn out that the best place to do that is not in this crate.
For instance, curve25519-dalek
intentionally does not provide any FFI: it's meant as a mid-level library and makes heavy use of Rust's capabilities to provide a safe and well-designed API. Instead, a specific construction (like ed25519-dalek
) uses it, and provides a minimal FFI interface for that construction.
In this case, I think it might make sense to provide an basic FFI interface for single rangeproof creation and verification, probably with a fixed customization string. But I'm much less sure that it would make sense to provide an FFI interface for the more modular parts of our API, like the proof transcripts, or the aggregation API --- people who want to use those building blocks should probably assemble them in Rust, and provide their own FFI.
In any case, I think we should wait to provide any FFI until we've stabilized our API and our implementation, since right now this code isn't ready for production use, and I think that FFI should come when it is.
Yeah, where to draw the FFI boundary is not very clear. I agree that it does not make sense to have FFI for curve25519-dalek
and it makes sense to have FFI for ed25519-dalek
. As for a signature/zkp scheme based on ProofTranscript instead of "we'll just hash your message blob", it's an open question.
I will try to justify my intuition behind making ProofTranscript as a native part of the FFI (no strong preference, just my thoughts so far). I feel there's a benefit to have an explicit tool for wiring multiple distinct crypto-protocols together in a project where you are not very likely to implement the whole protocol in Rust. For instance, PT API allows safely pinning additional proofs on top of previous proofs, while w/o PT you'd have to carefully hash the relevant protocol 1 inputs manually (repeating the the similar hashing performed inside protocol 1 already) with your message before passing the result to protocol 2. In some cases such composition will be a whole protocol implemented all in Rust w/o need for PT FFI (like IPP is part of RangeProof), but i'm not sure it's realistic to expect that universally across many projects. At the same time, PT is not as complex as generics, algebraic types or different representations (like there are different scalar and point representations) for having an FFI. It'd be just an extra opaque entity (a pointer) to pass around.
Example: lets say you have a Bitcoin+CT transaction. Let's say it's all written in C++ as a part of a bigger implementation. And your goal is to verify some Schnorr signatures in M inputs and verify some range proofs in the N outputs. If you have a proof transcript (PT) object, you can safely tie all the signatures to right portions of the transaction, cloning ("forking") the transcript at the right points to allow independence of signatures where needed. So if you have a multisig script, you'd just fork the PT and pass it for each pubkey, where it will be customized per-signature by the pubkey commitment. This will liberate the protocol designer from having to invent their own (pre)hashing scheme and domain separation. They will use higher-level concepts: pt.commit(data)
and pt.clone()
. And then if Bitcoin protocol needs to pile some other signature/zkp sub-protocol on top of the above, it can simply pass the mutated pt
into that sub-protocol.
A couple of notes after talking about this issue with @hdevalence and @cathieyun:
Yeah, this all makes sense. It's probably best to get a few example use cases before settling on an FFI API, especially prior to stabilization. I'll try fiddle around with the use case I was thinking of this weekend.
Changed title so that this becomes the tracking issue for FFI
You could just have several "useful example" crates that each provide a FFI for C, and eventually aid for JS usage. In that way, anyone could adapt a project by forking one of your examples, and just expand the FFI for C as they go.
Hi - does anyone know of a github project where the bulletproofs library has been used in a Java/Scala project? I'm looking for examples so I can figure out how to do this. Thanks.
WARNING: This is an early request-for-comments version of the proposal. Please discuss.
Problem
Rust is great for engineering complex cryptographic schemes: its memory model and type system enable us to make complex protocol easy and safe to use (example), while not compromising on performance (example). However, people write applications in other languages, most notably in C/C++, JavaScript, Go, Java, Swift etc. So for the world to actually enjoy safe and performant cryptography, we need to integrate well with all these environments.
Strategy
Does not scale: re-implementing multiple layers of the architecture, from finite field arithmetic in the curve25519-dalek to zero-knowledge proofs in Ristretto-bulletproofs.
Does scale: adding C/wasm API and language bindings for higher-level protocols, as they have more narrow API and do not expose users to raw cryptography unnecessarily.
Maintenance
For every project it probably makes sense to keep all language bindings in its repository to make sure the bindings stay in sync with the Rust API. Keeping language bindings in distinct repositories is a sure path to divergence of APIs and undermining trust of the users.
How can it work? Users of the project can submit PRs to add bindings for their language, together with a test suit. Project developers will be able to get automatic feedback whether their updates are compatible with all the language bindings, and can even add necessary fixes themselves (as the types passing FFI boundary should be pretty simple: integers, byte arrays and opaque pointers to structures defined in Rust).
Where to draw FFI boundary?
Do we expose a
ProofTranscript
object, or a simpler "message" buffer? Do we expose MPC API for aggregated proofs?Safety considerations: It's probably safer to not expose stateful pieces such as proof transcript, exposing instead simpler interfaces like "give us customization/msg string, secrets, and here's a proof blob for you". For things like aggregation MPC, the Rust type system and memory ownership allows safe use of all Party/Dealer types, but it would not be the case if exposed via FFI to less strict languages. E.g. in Ruby/JS/Java/Go one would be able to accidentally reuse some values and cause replay of the protocol, leading to secrets' recovery.
Ergonomics considerations: Generally, people are used to simpler one-shot APIs and comfortable with ad-hoc hashing schemes, so explicit PT will feel overly complicated, unless you know what you are doing.
Therefore, leaving sophisticated API in Rust for now is good for safety and invites people to compose their schemes in Rust, providing their own FFI on top. We can revisit this later. Either people will do more crypto in Rust (good choice!), and/or there will be extra more sophisticated FFI to do composition of protocols in foreign languages.
Checklist
wasm-bindgen
generates all FFI boilerplate directly from Rust declarations.Thanks to @domluna for raising the question in #95.