|
| 1 | +- Feature Name: language-bindings |
| 2 | +- Start Date: 2019-03-14 |
| 3 | +- RFC PR: (leave this empty) |
| 4 | +- Ursa Issue: (leave this empty) |
| 5 | +- Version: 1 |
| 6 | + |
| 7 | +# Summary |
| 8 | +[summary]: #summary |
| 9 | + |
| 10 | +The Rust programming language was chosen so Ursa can be portable and |
| 11 | +consumed by multiple programming languages. This RFC details how this is |
| 12 | +to be done. |
| 13 | + |
| 14 | +# Motivation |
| 15 | +[motivation]: #motivation |
| 16 | + |
| 17 | +Hyperledger projects will want to start using Ursa. Those that use Rust |
| 18 | +will have immediate access to the library's functionality. Those projects |
| 19 | +using other programming languages will need other methods. |
| 20 | + |
| 21 | +# Guide-level explanation |
| 22 | +[guide-level-explanation]: #guide-level-explanation |
| 23 | + |
| 24 | +The Rust programming language allows exporting functions to be called |
| 25 | +from in other languages. Most programming languages can consume "C" |
| 26 | +callable libraries. This RFC covers how Ursa will enable its code to be |
| 27 | +consumable by other languages. |
| 28 | + |
| 29 | +When exporting to other languages there are two types of wrappers that |
| 30 | +can be created: thin and idiomatic. Thin wrappers are just language bindings and shall be called |
| 31 | +bindings. Example code written |
| 32 | +in Rust below will be used to illustrate the difference. |
| 33 | + |
| 34 | +```rust |
| 35 | +pub struct Ed25519 {} |
| 36 | + |
| 37 | +impl Ed25519 { |
| 38 | + pub fn sign(message: &[u8], private_key: &[u8]) -> Vec<u8> { |
| 39 | + ... |
| 40 | + } |
| 41 | + pub fn verify(message: &[u8], public_key: &[u8], signature: &[u8]) -> bool { |
| 42 | + ... |
| 43 | + } |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +The purpose of the wrapper is to expose `sign` and `verify`. Bindings |
| 48 | +will be the simplest method like so: |
| 49 | + |
| 50 | +```c |
| 51 | +int ursa_bls_sign(unsigned char *signature, const unsigned long long signature_len, |
| 52 | + const unsigned char *const message, const unsigned long long message_length, |
| 53 | + const unsigned char *const private_key, const unsigned long long private_key_length); |
| 54 | + |
| 55 | +int ursa_bls_verify(const unsigned char *const message, const unsigned long long message_length, |
| 56 | + const unsigned char *const public_key, const unsigned long long public_key_length, |
| 57 | + const unsigned char *const signature, const unsigned long long signature_length); |
| 58 | +``` |
| 59 | +
|
| 60 | +The C code now exposes those two functions so C# can use them via the binding |
| 61 | +
|
| 62 | +```csharp |
| 63 | +using System; |
| 64 | +using System.Runtime.InteropServices; |
| 65 | +
|
| 66 | +namespace Hyperledger.Ursa.Api |
| 67 | +{ |
| 68 | + internal static class NativeMethods |
| 69 | + { |
| 70 | + [DllImport("ursa", CharSet = CharSet.Ansi)] |
| 71 | + internal static extern int ursa_bls_sign(out byte[] signature, long signature_len, |
| 72 | + byte[] message, long message_length, |
| 73 | + byte[] private_key, long private_key_length); |
| 74 | +
|
| 75 | + [DllImport("ursa", CharSet = CharSet.Ansi)] |
| 76 | + internal static extern int ursa_bls_verify(byte[] message, long message_length, |
| 77 | + byte[] public_key, long public_key_length, |
| 78 | + byte[] signature, long signature_length); |
| 79 | + } |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +Using bindings are not desirable by end users because it requires more knowledge |
| 84 | +about the native library than they perhaps want to know. It also does not allow |
| 85 | +language developers to use what they are most familiar with. However, bindings |
| 86 | +are required because they handle marshaling between the two languages. Idiomatic wrappers |
| 87 | +are written to hide the nastiness of the bindings and allow language developers to |
| 88 | +stick to what they like and are familiar with. Continuing the example above, an idiomatic |
| 89 | +wrapper can be written like this |
| 90 | + |
| 91 | +```csharp |
| 92 | +using System; |
| 93 | +using System.Text; |
| 94 | +using System.Runtime.InteropServices; |
| 95 | + |
| 96 | +using Hyperledger.Ursa.Api; |
| 97 | + |
| 98 | +namespace Bls |
| 99 | +{ |
| 100 | + public static class Bls |
| 101 | + { |
| 102 | + private const int SIGNATURE_BYTES = 32; |
| 103 | + |
| 104 | + public function byte[] Sign(string message, byte[] privateKey) |
| 105 | + { |
| 106 | + return Sign(Encoding.UTF8.GetBytes(message), private_key); |
| 107 | + } |
| 108 | + |
| 109 | + public function byte[] Sign(byte[] message, byte[] privateKey) |
| 110 | + { |
| 111 | + var signature = new byte[SIGNATURE_BYTES]; |
| 112 | + |
| 113 | + if (NativeMethods.ursa_bls_sign(out signature, SIGNATURE_BYTES, message, message.Length, privateKey, privateKey.Length) == 0) |
| 114 | + { |
| 115 | + return signature; |
| 116 | + } |
| 117 | + throw new Exception("An error occurred while signing message"); |
| 118 | + } |
| 119 | + |
| 120 | + public function bool Verify(string message, byte[] publicKey, byte[] signature) |
| 121 | + { |
| 122 | + return Verify(Encoding.UTF8.GetBytes(message), publicKey, signature); |
| 123 | + } |
| 124 | + |
| 125 | + public function bool Verify(byte[] message, byte[] publicKey, byte[] signature) |
| 126 | + { |
| 127 | + switch (NativeMethods.ursa_bls_verify(message, message.Length, |
| 128 | + privateKey, privateKey.Length, |
| 129 | + signature, signature.Length)) |
| 130 | + { |
| 131 | + case 0: return true; |
| 132 | + default: return false; |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +The idiomatic wrapper provides more logic and parameters for convenience than the thin wrapper does. |
| 140 | +It can also be expanded to include other types but ultimately maps all inputs to |
| 141 | +the expected binding types. |
| 142 | + |
| 143 | +Bindings are composed of many functions using the [foreign function interface (FFI)](https://doc.rust-lang.org/nomicon/ffi.html). |
| 144 | + |
| 145 | +# Reference-level explanation |
| 146 | +[reference-level-explanation]: #reference-level-explanation |
| 147 | + |
| 148 | +Bindings can be problematic to write by hand and maintain as APIs change. |
| 149 | +It is desirable to have bindings generated programmatically. |
| 150 | +Rust supports generating FFI using `bindgen`. `bindgen` can map to other languages like |
| 151 | +C and WASM. Thus bindings should be created and maintained as much as possible using features like `bindgen`. |
| 152 | +Bindings should be included in the Ursa project itself in a folder called `wrappers`. This will allow them to |
| 153 | +stay in-sync with any other changes to the core library. Rust also offers the [ffi-support](https://crates.io/crates/ffi-support) crate. The crate exposes |
| 154 | +Rust objects in three ways: |
| 155 | + |
| 156 | +- Serialization methods like proto or flat buffers or JSON. |
| 157 | +- HandleMaps that return handle Id's to callers |
| 158 | +- Converts between raw Rust native types to C native types. |
| 159 | + |
| 160 | +Bindings should use the best appropriate option for the given language. For example, WASM must use serialization but Python could use any of them. |
| 161 | + |
| 162 | +Idiomatic wrappers usually cannot be generated by hand as they require more in depth language experience |
| 163 | +than automatic generators can produce. Therefore, idiomatic wrappers should be written by project contributors |
| 164 | +and maintained in separate repositories. These wrappers also SHOULD include tests for interacting with |
| 165 | +native Ursa. |
| 166 | + |
| 167 | +Here are general guidelines for producing wrappers: |
| 168 | + |
| 169 | +`<language>` should follow the naming convention as described [here](https://support.codebasehq.com/articles/tips-tricks/syntax-highlighting-in-markdown) |
| 170 | + |
| 171 | +- Bindings |
| 172 | + - Produced by code |
| 173 | + - Output into the `libursa/bindings/<language>/folder` |
| 174 | +- Idiomatic wrappers |
| 175 | + - Written by developers |
| 176 | + - Implemented as best deemed by contributors/community |
| 177 | + - Stored in separate repositories using the naming convention `ursa-wrapper-<language>` |
| 178 | + - Keep certain things invariant |
| 179 | + - The reason is to make wrappers similar to one another, and to avoid unnecessary documentation burden or mental model translation between wrappers (which may have light doc) and ursa (where the investment in doc will be substantial). |
| 180 | + - If someone learns how to use a wrapper of the API in language 1, and then go look at the wrapper of the API in language 2, one should see the same basic concepts, and be able to predict the function names one should call and the sequence of calls one should use. |
| 181 | + - Each wrapper should preserve all the terminology of the main interface: don't change `encrypt` to `seal` or `decrypt` to `open` |
| 182 | + - All preconditions on parameters, all data formats (e.g., json, config files, etc), all constraints on input should be identical in all wrappers and in the base API. |
| 183 | + - The "level" of the wrapper API can be generally "higher" than the C-callable functions, but it should expose the same amount of functionality and flexibility as the C-callable functions, so the higher level functions, if they exist, should be an addition to lower-level ones, not substitutes for them. In other words, helpers and convenience methods are great, but don’t allow them to entirely hide the core functions which provide granular control. |
| 184 | + - The wrapper should document the earliest and latest version of ursa that it knows to be compatible. |
| 185 | + - The wrapper should document what platforms it targets, what use cases it's built for, etc. A wrapper should be able to find ursa using default OS methods (e.g., in the system PATH), but should also provide a way for a specific path to ursa to be specified, such that the wrapper can work either from an OS-wide install of ursa or a version in a particular directory. |
| 186 | + |
| 187 | +# Drawbacks |
| 188 | +[drawbacks]: #drawbacks |
| 189 | + |
| 190 | +- Some developers may want all ursa code written in the language of their needs to avoid issues presented by FFI. |
| 191 | +- Some languages cannot generate bindings yet using `bindgen`. |
| 192 | + |
| 193 | +# Prior art |
| 194 | +[prior-art]: #prior-art |
| 195 | + |
| 196 | +Languages have consumed C callable libraries for many years. Other languages have also compiled to C callable libraries like other system languages. |
| 197 | + |
| 198 | +# Unresolved questions |
| 199 | +[unresolved]: #unresolved-questions |
| 200 | + |
| 201 | +# Changelog |
| 202 | +[changelog]: #changelog |
| 203 | + |
| 204 | +- [14 Mar 2019] - v1 - Initial version |
0 commit comments