Skip to content
This repository was archived by the owner on Apr 27, 2023. It is now read-only.

Commit bd9ef93

Browse files
committed
Merge branch 'master' of github.com:hyperledger/ursa-rfcs into zmix
Signed-off-by: Michael Lodder <redmike7@gmail.com>
2 parents ce856ec + 657372b commit bd9ef93

File tree

1 file changed

+204
-0
lines changed

1 file changed

+204
-0
lines changed

text/language-bindings/README.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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

Comments
 (0)