├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── MATH.md
├── README.md
├── go
├── Makefile
├── go.mod
├── go.sum
└── pkg
│ └── oberon
│ ├── blinding.go
│ ├── blinding_test.go
│ ├── proof.go
│ ├── proof_test.go
│ ├── public_key.go
│ ├── secret_key.go
│ ├── secret_key_test.go
│ ├── token.go
│ ├── token_test.go
│ ├── util.go
│ └── util_test.go
├── img
├── one-pass.png
└── three-pass.png
├── include
└── oberon.h
├── nodejs
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── package.json
└── src
│ └── lib.rs
├── python
├── .gitignore
├── LICENSE
├── build.sh
├── oberon
│ ├── __init__.py
│ ├── bindings.py
│ ├── classes.py
│ ├── error.py
│ └── version.py
├── readme.md
├── setup.cfg
└── setup.py
├── rust
├── Cargo.toml
├── Cargo.toml.python
├── Dockerfile
├── Dockerfile.php
├── Dockerfile.python
├── LICENSE
├── benches
│ └── random.rs
├── examples
│ └── random_test.rs
├── publish_python.sh
├── pyproject.toml
├── src
│ ├── blinding.rs
│ ├── ffi.rs
│ ├── lib.rs
│ ├── php.rs
│ ├── proof.rs
│ ├── public_key.rs
│ ├── python.rs
│ ├── secret_key.rs
│ ├── token.rs
│ ├── util.rs
│ └── web.rs
├── test.php
└── tests
│ ├── blinding.rs
│ ├── common
│ └── mod.rs
│ ├── proof.rs
│ ├── public_key.rs
│ ├── secret_key.rs
│ └── token.rs
└── test_vectors
├── blinding.json
├── proof.json
└── token.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | target/
4 | build/
5 | *.swp
6 |
7 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
8 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
9 | Cargo.lock
10 |
11 | # These are backup files generated by rustfmt
12 | **/*.rs.bk
13 |
14 |
15 | # Added by cargo
16 | #
17 | # already existing elements were commented out
18 |
19 | #Cargo.lock
20 | .idea/
21 | .env/
22 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Oberon
2 |
3 | Oberon is Apache 2.0 licensed and accepts contributions via
4 | [GitHub](https://github.com/mikelodder7/oberon) pull requests.
5 |
6 | # Ways to contribute to Oberon
7 |
8 | - Bugs or issues: Report problems or defects as github issues
9 | - Features and enhancements: Provide expanded capabilities or optimizations
10 | - Documentation: Improve existing documentation or create new information
11 | - Tests for events and results:
12 | - Functional
13 | - Performance
14 | - Usability
15 | - Security
16 | - Localization
17 | - Deployability
18 |
19 | # The Commit Process
20 |
21 | When contributing code, please follow these guidelines:
22 |
23 | - Fork the repository and make your changes in a feature branch
24 | - Include unit and integration tests for any new features and updates to existing tests
25 | - Ensure that the unit and integration tests run successfully.
26 | - Check that the lint tests pass
27 |
28 | ## Important
29 | Use `git rebase origin/master` to limit creating merge commits
30 |
31 | ## Commit Email Address
32 | Your commit email address must match your GitHub or GitLab email address. For more information, see https://help.github.com/articles/setting-your-commit-email-address-in-git/.
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/MATH.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ## Primitives
5 |
6 | ### Curve and Bilinear Maps
7 |
8 | The protocol uses pairing-friendly curves as the basic building block under the hood.
9 |
10 | This implementation uses [BLS12-381](https://hackmd.io/@benjaminion/bls12-381) but can easily change to any other pairing-friendly curve.
11 |
12 | The curve
parameters are denoted as
13 |
14 | -
: The security parameter in bits.
15 | -
: The field modulus
16 | -
: The subgroup order
17 | -
: Points in the cyclic group of order
18 | -
: Points in the multiplicative group of order
19 | -
: A pairing function that takes
and
and returns a result in the multiplicative group
in
20 | -
: The base point in
21 | -
: The base point in
22 | -
: The point at infinity in
23 | -
: The point at infinity in
24 | -
: The point at infinity in
25 |
26 | Scalars operate in
and are denoted as lower case letters.
27 | Scalars are represented with 32 bytes with BLS12-381.
28 |
29 | Points operating in
are denoted as capital letters.
30 |
points in BLS12-381 are 48 bytes compressed and 96 bytes uncompressed.
31 |
32 | Points operating in
are denoted as capital letters with a wide tilde.
33 |
points in BLS12-381 are 96 bytes compressed and 192 bytes uncompressed.
34 |
35 | ### Hash to Curve
36 |
37 | Oberon uses [Hash to curve](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/)
38 | to map arbitrary byte sequences to random points with unknown discrete logs.
39 |
40 | This is denoted as
for hashing to a point in
.
41 |
42 | The hash to curve standard demands a unique DST to be defined. Oberon uses
43 |
44 | `OBERON_BLS12381G1_XOF:SHAKE-256_SSWU_RO_`
45 |
46 | ### Hash to Field
47 |
48 | Oberon hashes arbitrary byte sequences to a field element. The tricky part here is
49 | to generate enough bytes such that the result is distributed uniformly random.
50 |
51 | A common approach is to use SHA256 to hash to byte sequence then reduce modulo
.
52 | However, this results in a biased result that isn't uniform. Instead more bytes
53 | should be generated then reduced modulo
.
54 | The number of bytes is calculated with L = ceil((ceil(log2(p)) + k) / 8).
55 | For BLS12-381 this is L=48 bytes.
56 |
57 | This implementation uses SHAKE-256 to output 48 bytes.
58 |
59 | Hash to field is denoted as
.
60 |
61 | Hash to field uses the domain separation tag `OBERON_BLS12381FQ_XOF:SHAKE-256_`
62 |
63 | ### Signatures
64 |
65 | Oberon uses [BLS keys](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/)
66 | in combination with [Pointcheval Saunders](https://eprint.iacr.org/2015/525) (PS) signatures
67 | with improvements in [Reassessing Security of PS signatures](https://eprint.iacr.org/2017/1197) to be secure under
68 | Existential Unforgeability against Adaptively Chosen Message Attacks (EUF-CMA).
69 |
70 | ### Notations
71 |
72 | - a || b: is the byte concatenation of two elements a, b
73 | -
: is a random number in the field
74 | -
is the user’s identification string
75 |
76 | ## Algorithms
77 |
78 | Oberon has the following algorithms:
79 |
80 | ### KeyGen
81 |
82 | By default, Oberon only signs a user’s identity string
, but PS signatures
83 | support many attributes if needed with the tradeoff that keys get bigger but not the token.
84 |
85 | KeyGen(
)
86 |
87 | Generate BLS keys and set them for
88 |
89 |
90 |
91 |
92 |
93 | The output is
94 |
95 | The secret key
and is 96 bytes.
96 |
97 | The public key
and is 288 bytes.
98 |
99 | ### IdToInternals
100 |
101 | This function maps the user's identity string
to the various internals and checks if they are valid.
102 |
103 | IdToInternals(
)
104 |
105 | ```math
106 | \begin{align}
107 | m &= H_{\mathbb{Z}_q}(id) ;& \text{if}\ m &= 0\ \text{abort} \\
108 | m' &= H_{\mathbb{Z}_q}(m) ;& \text{if}\ m' &= 0\ \text{abort} \\
109 | U &= H_{\mathbb{G}_1}(m') ;& \text{if}\ U &= 1_{\mathbb{G}_1}\ \text{abort} \\
110 | \end{align}
111 | ```
112 |
113 | The function outputs (
,
,
)
114 |
115 | ### Sign
116 |
117 | Sign creates a token to be given to a user and works as follows
118 |
119 | Sign(
,
)
120 |
121 | ```math
122 | \begin{align}
123 | m, m', U &= \text{IdToInternals}(id) \\
124 | \sigma &= (x + m.y + m'.w)\cdot U ;& \text{if}\ \sigma &= 1_{\mathbb{G}_1}\ \text{abort} \\
125 | \end{align}
126 | ```
127 |
128 | Output token is
129 |
130 | ### Blinding factor
131 |
132 | Oberon can use a blinding factor in combination with the normal token
133 | to *blind* the original token such that without knowledge of the 2nd factor,
134 | the token is useless.
135 | Blinding factors can be computed by using HG1 on any arbitrary input.
136 |
137 | For example, the user could select a 6-digit pin that needs to be entered
138 | each time they want to use it. To require the pin to use the token, the following can be computed
139 |
140 | -
141 | -
142 |
143 | Store
144 |
145 | Multiple blinding factors can be applied in a similar manner. Each blinding factor
146 | can be different based on the platform where the token resides.
147 |
148 | ### Verify
149 |
150 | Verify takes a token and checks its validity. Meant to be run by the token holder
151 | since the token should never be disclosed to anyone.
152 |
153 | Verify(
,
,
)
154 |
155 |
156 | ```math
157 | \begin{align}
158 | m, m', U &= \text{IdToInternals}(id) \\
159 | e(U, &\widetilde{X} + m \cdot \widetilde{Y} + m' \cdot \widetilde{W}).e(\sigma, -\widetilde{P}) = 1_{\mathbb{G}_T}
160 | \end{align}
161 | ```
162 |
163 |
can be cached by the holder for performance if desired in which case
164 | those steps can be skipped.
165 |
166 | ### Prove
167 |
168 | Prove creates a zero-knowledge proof of a valid token instead of sending the token itself.
169 | This allows the token to be reused while minimizing the risk of correlation.
170 |
171 | Below is the algorithm for Prove assuming a blind factor with a pin.
172 |
173 | Prove(
,
,
)
174 |
175 |
can be a timestamp or publicly verifiable value like the latest Bitcoin hash.
176 |
177 |
178 | ```math
179 | \begin{align}
180 | m, m', U &= \text{IdToInternals}(id) \\
181 | r &\xleftarrow{\$} \mathbb{Z}_q* ;& \text{if}\ r &= 0\ \text{abort} \\
182 | U' &= r \cdot U ;& \text{if}\ U' &= 1_{\mathbb{G}_1}\ \text{abort} \\
183 | t &= H_{\mathbb{Z}_q}(U' || n) ;& \text{if}\ t &= 0\ \text{abort} \\
184 | Z &= -(r + t) \cdot (\sigma' + B) ;& \text{if}\ Z &= 1_{\mathbb{G}_1}\ \text{abort} \\
185 | \tau &= U', Z
186 | \end{align}
187 | ```
188 |
189 | ### Open
190 |
191 | Open validates whether a proof is valid against a specific public key and is fresh enough.
192 |
193 | Open(
,
,
)
194 |
195 | ```math
196 | \begin{align}
197 | & & \text{if}\ U' = 1_{\mathbb{G}_1}\ \text{abort} \\
198 | & & \text{if}\ Z = 1_{\mathbb{G}_1}\ \text{abort} \\
199 | m, m', U &= \text{IdToInternals}(id) \\
200 | t &= H_{\mathbb{Z}_q}(U' || n) ;& \text{if}\ t = 0\ \text{abort} \\
201 | e(&U' + t\cdot U, \widetilde{X} + m \cdot \widetilde{Y} + m' \cdot \widetilde{W})e(Z, \widetilde{P}) == 1_{\mathbb{G}_T}
202 | \end{align}
203 | ```
204 |
205 | ## Other notes
206 |
207 | PS signatures support blind signatures methods such that
could be blinded before
208 | being signed by the token issuer.
209 |
210 | They also support multiple attributes that can be added to the signature with the cost of an additional
211 | BLS keypair per attribute.
212 |
213 | Oberon is meant to be simple and for now doesn't handle these features but might in future work.
214 |
215 | ## Threshold
216 |
217 | Since the keys are BLS based, they can use any suitable threshold key gen and sign technique.
218 |
219 | This process should be handled outside of Oberon. Another crate will probably be created for this.
220 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![Crate][crate-image]][crate-link]
2 | [![Docs][docs-image]][docs-link]
3 | ![Apache 2.0][license-image]
4 |
5 | # Oberon
6 | A succinct ZKP protocol for authentication. It works by using techniques similar to
7 | Identity-Based/Attribute-Based signatures.
8 |
9 | **Executive Summary**: Oberon allows endpoints to issue multi-factor capable
10 | tokens to consumers who can prove their validity *without* disclosing the
11 | tokens themselves and without requiring email, SMS, or authenticator apps.
12 | Endpoints only need to store a single public key and not any tokens. An
13 | attacker that breaks into the server doesn't have any password/token files to
14 | steal and only would see a public key. The proof of token validity is only 96
15 | bytes while the token itself is only 48 bytes. The issuing party and verifying
16 | servers can be separate entities.
17 |
18 | ## Languages
19 |
20 | Oberon is implemented for Rust, Go, WebAssembly, PHP8, Python, and C/C++ via FFI.
21 |
22 | ### Building
23 |
24 | #### Rust
25 | ```bash
26 | cargo build --release
27 | ```
28 |
29 | The binary is created in `target/release/liboberon.so`
30 |
31 | #### WebAssembly
32 | ```bash
33 | wasm-pack build --target=web -- --features=wasm
34 | ```
35 |
36 | #### PHP8
37 | You must have PHP8 installed to complete the build. The `Dockerfile` in this repo
38 | already sets up the necessary environment.
39 |
40 | ```bash
41 | docker build -t oberon-php -f Dockerfile.php .
42 | docker run --rm -v $PWD:/data -w /data -t oberon-php cargo build --release --features=php
43 | ```
44 |
45 | #### Python
46 |
47 | Building python requires Python 3.7 and up and [maturin](https://github.com/PyO3/maturin)
48 |
49 | ```bash
50 | maturin develop --cargo-extra-args="--features=python"
51 | ```
52 |
53 | #### C/C++
54 |
55 | To expose the non-mangled functions that are compatible with C/C++ use
56 | ```bash
57 | cargo build --release --features=ffi
58 | ```
59 |
60 | ## In depth details
61 |
62 | The cryptography can be found [here](MATH.md)
63 |
64 | First steps require generating keys
65 |
66 | The secret key can be generated using distributed key generation methods also but is outside the scope of this crate.
67 |
68 | The public key can be given to any party that needs to verify tokens and token proofs.
69 |
70 | Tokens are generated for parties that need to authenticate. API endpoints or users can be token holders.
71 |
72 | ```rust
73 | use oberon::*;
74 | use rand::prelude::*;
75 |
76 | fn main() {
77 | let mut rng = thread_rng();
78 | let sk = SecretKey::new(&mut rng);
79 | let pk = PublicKey::from(&sk);
80 |
81 | // identifier for a user
82 | let id = b"abc@example.com";
83 | let token = Token::new(&sk, id).unwrap(); //only None if identifier yields invalid data
84 |
85 | assert_eq!(token.verify(pk, id).unwrap_u8(), 1u8);
86 |
87 | // Generated by the verifier
88 | let nonce = b"123456789012345678901234567890";
89 |
90 | // Token holder makes a proof, no blindings (more on that later)
91 | let proof = Proof::new(&token, &[], id, nonce, &mut rng).unwrap(); // only None if identifier yields invalid data
92 |
93 | // Verifier receives the proof
94 | assert_eq!(proof.open(pk, id, nonce).unwrap_u8(), 1u8);
95 |
96 | // Blindings can be applied to support multi-factor authentication and keeps the token from being stored in plaintext.
97 | // Pin number
98 | let b1 = Blinding::new(b"1234");
99 |
100 | // HSM secret
101 | let b2 = Blinding::new(b"0102d9d1-4777-40e4-9217-1e2d9591706c");
102 |
103 | let blinding_token = token - b1;
104 | let blinding_token = blinding_token - b2;
105 |
106 | // Token holder makes a proof, with two blindings
107 | let proof = Proof::new(&blinding_token, &[b1, b2], id, nonce, &mut rng).unwrap(); // only None if identifier yields invalid data
108 |
109 |
110 | // Verifier receives the proof, no blindings required
111 | assert_eq!(proof.open(pk, id, nonce).unwrap_u8(), 1u8);
112 | }
113 | ```
114 |
115 | The idea is that the protocol can be used in a three-pass model like logging into a service or a single-pass model
116 | for API endpoint use.
117 |
118 | Three pass model
119 |
120 | 
121 |
122 |
123 | One pass model
124 |
125 | 
126 |
127 | [//]: # (badges)
128 |
129 | [crate-image]: https://img.shields.io/crates/v/oberon.svg
130 | [crate-link]: https://crates.io/crates/oberon
131 | [docs-image]: https://docs.rs/oberon/badge.svg
132 | [docs-link]: https://docs.rs/oberon/
133 | [license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg
134 |
--------------------------------------------------------------------------------
/go/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: build clean cover deflake deps fmt lint test test-clean
2 |
3 | GO=${GOENV} go
4 |
5 | TEST_CLAUSE= $(if ${TEST}, -run ${TEST})
6 | COVERAGE_OUT=coverage.out
7 | PACKAGE=./...
8 |
9 | .PHONY: build
10 | build:
11 | ${GO} build ${PACKAGE}
12 |
13 | .PHONE: clean
14 | clean:
15 | ${GO} clean -cache -modcache -i -r
16 | rm -f ${COVERAGE_OUT}
17 |
18 | .PHONY: cover
19 | cover:
20 | ${GO} test -short -coverprofile=${COVERAGE_OUT} ${PACKAGE}
21 | ${GO} tool cover -html=${COVERAGE_OUT}
22 |
23 | .PHONY: deps
24 | deps:
25 | ${GO} mod tidy
26 | if [ -d "./bls12-381" ] ; then git submodule update bls12-381; fi
27 |
28 | .PHONY: deflake
29 | deflake:
30 | ${GO} test -count=1000 -timeout 0 ${TEST_CLAUSE} ${PACKAGE}
31 |
32 | .PHONY: fmt
33 | fmt:
34 | ${GO} fmt ${PACKAGE}
35 |
36 | .PHONY: lint
37 | ${GO} vet ${PACKAGE}
38 |
39 | .PHONY: test
40 | ${GO} test ${TEST_CLAUSE} ${PACKAGE}
41 |
42 | .PHONY: test-clean
43 | ${GO} clean -testcache && ${GO} test -count=1 ${TEST_CLAUSE} ${PACKAGE}
--------------------------------------------------------------------------------
/go/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mikelodder7/oberon/go
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/coinbase/kryptology v1.8.0
7 | github.com/stretchr/testify v1.7.0
8 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
9 | )
10 |
11 | require (
12 | filippo.io/edwards25519 v1.0.0-rc.1 // indirect
13 | github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401 // indirect
14 | github.com/bwesterb/go-ristretto v1.2.0 // indirect
15 | github.com/consensys/gnark-crypto v0.5.3 // indirect
16 | github.com/davecgh/go-spew v1.1.1 // indirect
17 | github.com/kr/pretty v0.3.0 // indirect
18 | github.com/pkg/errors v0.9.1 // indirect
19 | github.com/pmezard/go-difflib v1.0.0 // indirect
20 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
21 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
22 | )
23 |
--------------------------------------------------------------------------------
/go/go.sum:
--------------------------------------------------------------------------------
1 | filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
2 | filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
3 | git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw=
4 | git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA=
5 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
6 | github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
7 | github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401 h1:0tjUthKCaF8zwF9Qg7lfnep0xdo4n8WiFUfQPaMHX6g=
8 | github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs=
9 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
10 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
11 | github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
12 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
13 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
14 | github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
15 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
16 | github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
17 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
18 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
19 | github.com/bwesterb/go-ristretto v1.2.0 h1:xxWOVbN5m8NNKiSDZXE1jtZvZnC6JSJ9cYFADiZcWtw=
20 | github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
21 | github.com/coinbase/kryptology v1.8.0 h1:Aoq4gdTsJhSU3lNWsD5BWmFSz2pE0GlmrljaOxepdYY=
22 | github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI=
23 | github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
24 | github.com/consensys/gnark-crypto v0.5.3 h1:4xLFGZR3NWEH2zy+YzvzHicpToQR8FXFbfLNvpGB+rE=
25 | github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0=
26 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
27 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
28 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
29 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
30 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
31 | github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
32 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
33 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
34 | github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
35 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
36 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
37 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
38 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
39 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
40 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
41 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
42 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
43 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
44 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
45 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
46 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
47 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
48 | github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
49 | github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
50 | github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
51 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
52 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
53 | github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
54 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
55 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
56 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
57 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
58 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
59 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
60 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
61 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
62 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
63 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
64 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
65 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
66 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
67 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
68 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
69 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
70 | golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
71 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
72 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
73 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
74 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
75 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
76 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
77 | golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
78 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
79 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
80 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
81 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
82 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
83 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
84 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
85 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
86 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
87 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
88 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
89 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
90 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
91 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
92 | golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
93 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
94 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
95 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
96 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
97 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
98 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
99 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
100 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
101 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
102 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
103 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
104 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
105 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
106 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
107 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
108 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
109 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
110 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
111 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
112 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
113 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
114 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
115 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
116 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
117 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
118 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
119 | rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
120 |
--------------------------------------------------------------------------------
/go/pkg/oberon/blinding.go:
--------------------------------------------------------------------------------
1 | package oberon
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/coinbase/kryptology/pkg/core/curves"
7 | )
8 |
9 | type Blinding struct {
10 | Value *curves.PointBls12381G1
11 | }
12 |
13 | func NewBlinding(data []byte) (*Blinding, error) {
14 | b := new(Blinding)
15 | err := b.Create(data)
16 | return b, err
17 | }
18 |
19 | func (b *Blinding) Create(data []byte) error {
20 | p, err := hashToCurve(data)
21 | if err != nil {
22 | return err
23 | }
24 | b.Value = p
25 | return nil
26 | }
27 |
28 | func (b Blinding) MarshalBinary() ([]byte, error) {
29 | return b.Value.ToAffineCompressed(), nil
30 | }
31 |
32 | func (b *Blinding) UnmarshalBinary(data []byte) error {
33 | pt, err := curves.BLS12381G1().NewIdentityPoint().FromAffineCompressed(data)
34 | if err != nil {
35 | return err
36 | }
37 | if pt.IsIdentity() {
38 | return fmt.Errorf("invalid token")
39 | }
40 | b.Value, _ = pt.(*curves.PointBls12381G1)
41 | return nil
42 | }
43 |
44 | func (b Blinding) MarshalText() ([]byte, error) {
45 | return json.Marshal(b.Value.ToAffineCompressed())
46 | }
47 |
48 | func (b *Blinding) UnmarshalText(in []byte) error {
49 | var data [48]byte
50 | err := json.Unmarshal(in, &data)
51 | if err != nil {
52 | return err
53 | }
54 | pt, err := curves.BLS12381G1().NewIdentityPoint().FromAffineCompressed(data[:])
55 | if err != nil {
56 | return err
57 | }
58 | b.Value, _ = pt.(*curves.PointBls12381G1)
59 | return nil
60 | }
61 |
--------------------------------------------------------------------------------
/go/pkg/oberon/blinding_test.go:
--------------------------------------------------------------------------------
1 | package oberon
2 |
3 | import (
4 | crand "crypto/rand"
5 | "github.com/stretchr/testify/require"
6 | "testing"
7 | )
8 |
9 | var testId = []byte("oberon test identity")
10 |
11 | func TestBlindingWorks(t *testing.T) {
12 | blinding := new(Blinding)
13 | err := blinding.Create([]byte("1234"))
14 | require.NoError(t, err)
15 |
16 | sk, err := NewSecretKey(crand.Reader)
17 | require.NoError(t, err)
18 |
19 | token, err := sk.Sign(testId)
20 | require.NoError(t, err)
21 | blindedToken := new(Token).ApplyBlinding(token, blinding)
22 | require.False(t, token.Value.Equal(blindedToken.Value))
23 | require.True(t, new(Token).RemoveBlinding(blindedToken, blinding).Value.Equal(token.Value))
24 | }
25 |
--------------------------------------------------------------------------------
/go/pkg/oberon/proof.go:
--------------------------------------------------------------------------------
1 | package oberon
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/coinbase/kryptology/pkg/core/curves"
7 | "github.com/coinbase/kryptology/pkg/core/curves/native/bls12381"
8 | "io"
9 | )
10 |
11 | const proofBytes = 96
12 |
13 | type Proof struct {
14 | UTick, Z *curves.PointBls12381G1
15 | }
16 |
17 | func NewProof(token *Token, blindings []*Blinding, id, nonce []byte, rng io.Reader) (*Proof, error) {
18 | p := new(Proof)
19 | err := p.Create(token, blindings, id, nonce, rng)
20 | return p, err
21 | }
22 |
23 | func (p *Proof) Create(
24 | token *Token,
25 | blindings []*Blinding,
26 | id, nonce []byte,
27 | rng io.Reader,
28 | ) error {
29 | m, err := computeM(id)
30 | if err != nil {
31 | return err
32 | }
33 | mTick, err := computeMTick(m)
34 | if err != nil {
35 | return err
36 | }
37 | u, err := computeU(mTick)
38 | if err != nil {
39 | return err
40 | }
41 |
42 | r := genRndScalar(rng)
43 | uTick := u.Mul(r)
44 |
45 | t, err := hashToScalar([][]byte{
46 | uTick.ToAffineCompressed(),
47 | nonce,
48 | })
49 | if err != nil {
50 | return err
51 | }
52 |
53 | rat := r.Add(t)
54 | z := curves.BLS12381G1().NewIdentityPoint()
55 | z = z.Add(token.Value)
56 | for _, b := range blindings {
57 | z = z.Add(b.Value)
58 | }
59 | z = z.Mul(rat)
60 |
61 | p.UTick = uTick.(*curves.PointBls12381G1)
62 | p.Z = z.Neg().(*curves.PointBls12381G1)
63 | return nil
64 | }
65 |
66 | func (p Proof) Open(
67 | pk *PublicKey,
68 | id, nonce []byte,
69 | ) error {
70 | goodProof := isValidPointG1(p.Z)
71 | goodUTick := isValidPointG1(p.UTick)
72 |
73 | if !goodProof || !goodUTick {
74 | return fmt.Errorf("invalid proof")
75 | }
76 |
77 | m, err := computeM(id)
78 | if err != nil {
79 | return err
80 | }
81 | mTick, err := computeMTick(m)
82 | if err != nil {
83 | return err
84 | }
85 | u, err := computeU(mTick)
86 | if err != nil {
87 | return err
88 | }
89 | t, err := hashToScalar([][]byte{
90 | p.UTick.ToAffineCompressed(),
91 | nonce,
92 | })
93 | if err != nil {
94 | return err
95 | }
96 | lhs := p.UTick.Add(u.Mul(t)).(*curves.PointBls12381G1)
97 | rhs := pk.X.Add(pk.Y.Mul(m)).Add(pk.W.Mul(mTick)).(*curves.PointBls12381G2)
98 | g2 := curves.BLS12381G2().NewGeneratorPoint().(*curves.PointBls12381G2)
99 |
100 | engine := new(bls12381.Engine)
101 | engine.AddPair(lhs.Value, rhs.Value)
102 | engine.AddPair(p.Z.Value, g2.Value)
103 | if engine.Check() {
104 | return nil
105 | } else {
106 | return fmt.Errorf("check failed")
107 | }
108 | }
109 |
110 | func (p Proof) MarshalBinary() ([]byte, error) {
111 | var tmp [proofBytes]byte
112 | copy(tmp[:48], p.UTick.ToAffineCompressed())
113 | copy(tmp[48:], p.Z.ToAffineCompressed())
114 | return tmp[:], nil
115 | }
116 |
117 | func (p *Proof) UnmarshalBinary(in []byte) error {
118 | curve := curves.BLS12381(new(curves.PointBls12381G1))
119 | if len(in) != proofBytes {
120 | return fmt.Errorf("invalid length")
121 | }
122 | utick, err := curve.NewG1IdentityPoint().FromAffineCompressed(in[:48])
123 | if err != nil {
124 | return err
125 | }
126 | z, err := curve.NewG1IdentityPoint().FromAffineCompressed(in[48:])
127 | if err != nil {
128 | return err
129 | }
130 |
131 | Z, _ := z.(*curves.PointBls12381G1)
132 | UTick, _ := utick.(*curves.PointBls12381G1)
133 |
134 | goodProof := isValidPointG1(Z)
135 | goodUTick := isValidPointG1(UTick)
136 |
137 | if goodProof && goodUTick {
138 | p.Z = Z
139 | p.UTick = UTick
140 | return nil
141 | }
142 | return fmt.Errorf("invalid proof")
143 | }
144 |
145 | func (p Proof) MarshalText() ([]byte, error) {
146 | tmp := map[string][]byte{
147 | "z": p.Z.ToAffineCompressed(),
148 | "u_tick": p.UTick.ToAffineCompressed(),
149 | }
150 | return json.Marshal(&tmp)
151 | }
152 |
153 | func (p *Proof) UnmarshalText(in []byte) error {
154 | var tmp map[string][]byte
155 | var z, uTick *curves.PointBls12381G1
156 |
157 | curve := curves.BLS12381(new(curves.PointBls12381G1))
158 | err := json.Unmarshal(in, &tmp)
159 | if err != nil {
160 | return err
161 | }
162 | if proofBytes, ok := tmp["z"]; ok {
163 | pf, err := curve.NewG1IdentityPoint().FromAffineCompressed(proofBytes)
164 | if err != nil {
165 | return err
166 | }
167 | z, _ = pf.(*curves.PointBls12381G1)
168 | } else {
169 | return fmt.Errorf("missing expected map key 'proof'")
170 | }
171 |
172 | if uTickBytes, ok := tmp["u_tick"]; ok {
173 | ut, err := curve.NewG1IdentityPoint().FromAffineCompressed(uTickBytes)
174 | if err != nil {
175 | return err
176 | }
177 | uTick, _ = ut.(*curves.PointBls12381G1)
178 | } else {
179 | return fmt.Errorf("missing expected map key 'u_tick'")
180 | }
181 |
182 | goodProof := isValidPointG1(z)
183 | goodUTick := isValidPointG1(uTick)
184 | if goodProof && goodUTick {
185 | p.Z = z
186 | p.UTick = uTick
187 | return nil
188 | }
189 | return fmt.Errorf("invalid proof")
190 | }
191 |
192 | func genRndScalar(rng io.Reader) *curves.ScalarBls12381 {
193 | curve := curves.BLS12381G1()
194 | s := curve.NewScalar().Random(rng)
195 | for s.IsZero() || s.IsOne() {
196 | s = s.Random(rng)
197 | }
198 | r, _ := s.(*curves.ScalarBls12381)
199 | return r
200 | }
201 |
--------------------------------------------------------------------------------
/go/pkg/oberon/proof_test.go:
--------------------------------------------------------------------------------
1 | package oberon
2 |
3 | import (
4 | crand "crypto/rand"
5 | "github.com/stretchr/testify/require"
6 | "testing"
7 | )
8 |
9 | func TestProof(t *testing.T) {
10 | // Test values don't use as real world values
11 | nonce := []byte{138, 162, 3, 91, 76, 34, 240, 157, 149, 94, 93, 228, 214, 51, 50, 136}
12 | skBytes := []byte{180, 92, 239, 44, 240, 143, 149, 163, 45, 177, 22, 179, 146, 120, 129, 229, 78, 56, 70, 205, 251, 160, 140, 79, 159, 138, 6, 56, 250, 236, 176, 11, 70, 53, 138, 199, 245, 180, 223, 213, 128, 166, 122, 225, 67, 58, 138, 201, 19, 114, 57, 149, 70, 141, 31, 45, 180, 30, 208, 222, 234, 112, 21, 34, 37, 5, 163, 172, 96, 40, 81, 27, 89, 86, 163, 93, 15, 201, 200, 183, 157, 18, 134, 140, 156, 43, 79, 231, 42, 234, 198, 139, 130, 52, 176, 106}
13 | sk := new(SecretKey)
14 | err := sk.UnmarshalBinary(skBytes)
15 | require.NoError(t, err)
16 | pk := sk.PublicKey()
17 | require.NotNil(t, pk)
18 |
19 | token, err := NewToken(sk, testId)
20 | require.NoError(t, err)
21 | require.NotNil(t, token)
22 | require.NoError(t, pk.Verify(testId, token))
23 | blinding, err := NewBlinding([]byte("1234"))
24 | require.NoError(t, err)
25 | require.NotNil(t, blinding)
26 |
27 | blindedToken := new(Token).ApplyBlinding(token, blinding)
28 | proof, err := NewProof(blindedToken, []*Blinding{blinding}, testId, nonce, crand.Reader)
29 | require.NoError(t, err)
30 | require.NotNil(t, proof)
31 | require.NoError(t, proof.Open(pk, testId, nonce))
32 | require.Error(t, proof.Open(pk, []byte("wrong id"), nonce))
33 | require.Error(t, proof.Open(pk, testId, []byte("wrong nonce")))
34 | // No blindings
35 | proof, err = NewProof(blindedToken, []*Blinding{}, testId, nonce, crand.Reader)
36 | require.NoError(t, err)
37 | require.Error(t, proof.Open(pk, testId, nonce))
38 |
39 | blindedToken = token.ApplyBlinding(token, blinding)
40 | proof, err = NewProof(blindedToken, []*Blinding{blinding}, testId, nonce, crand.Reader)
41 | require.NoError(t, err)
42 | require.NotNil(t, proof)
43 | require.NoError(t, proof.Open(pk, testId, nonce))
44 | require.Error(t, proof.Open(pk, []byte("wrong id"), nonce))
45 | require.Error(t, proof.Open(pk, testId, []byte("wrong nonce")))
46 | // No blindings
47 | proof, err = NewProof(blindedToken, []*Blinding{}, testId, nonce, crand.Reader)
48 | require.NoError(t, err)
49 | require.Error(t, proof.Open(pk, testId, nonce))
50 | }
51 |
--------------------------------------------------------------------------------
/go/pkg/oberon/public_key.go:
--------------------------------------------------------------------------------
1 | package oberon
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/coinbase/kryptology/pkg/core/curves"
7 | )
8 |
9 | type PublicKey struct {
10 | W *curves.PointBls12381G2
11 | X *curves.PointBls12381G2
12 | Y *curves.PointBls12381G2
13 | }
14 |
15 | func (p *PublicKey) Verify(id []byte, t *Token) error {
16 | return t.Verify(p, id)
17 | }
18 |
19 | func (p *PublicKey) FromSecretKey(sk *SecretKey) {
20 | curve := curves.BLS12381G2()
21 | p.W, _ = curve.ScalarBaseMult(sk.W).(*curves.PointBls12381G2)
22 | p.X, _ = curve.ScalarBaseMult(sk.X).(*curves.PointBls12381G2)
23 | p.Y, _ = curve.ScalarBaseMult(sk.Y).(*curves.PointBls12381G2)
24 | }
25 |
26 | func (p PublicKey) MarshalBinary() ([]byte, error) {
27 | return append(append(p.W.ToAffineCompressed(), p.X.ToAffineCompressed()...), p.Y.ToAffineCompressed()...), nil
28 | }
29 |
30 | func (p *PublicKey) UnmarshalBinary(in []byte) error {
31 | if len(in) != 288 {
32 | return fmt.Errorf("invalid length")
33 | }
34 | curve := curves.BLS12381G2()
35 | w, err := curve.Point.FromAffineCompressed(in[:96])
36 | if err != nil {
37 | return err
38 | }
39 | x, err := curve.Point.FromAffineCompressed(in[96:192])
40 | if err != nil {
41 | return err
42 | }
43 | y, err := curve.Point.FromAffineCompressed(in[192:])
44 | if err != nil {
45 | return err
46 | }
47 |
48 | W, _ := w.(*curves.PointBls12381G2)
49 | X, _ := x.(*curves.PointBls12381G2)
50 | Y, _ := y.(*curves.PointBls12381G2)
51 |
52 | goodW := !w.IsIdentity()
53 | goodX := !x.IsIdentity()
54 | goodY := !y.IsIdentity()
55 |
56 | if goodW && goodX && goodY {
57 | p.W = W
58 | p.X = X
59 | p.Y = Y
60 | return nil
61 | }
62 | return fmt.Errorf("invalid public key")
63 | }
64 |
65 | func (p PublicKey) MarshalText() ([]byte, error) {
66 | tmp := map[string][]byte{
67 | "w": p.W.ToAffineCompressed(),
68 | "x": p.X.ToAffineCompressed(),
69 | "y": p.Y.ToAffineCompressed(),
70 | }
71 | return json.Marshal(&tmp)
72 | }
73 |
74 | func (p *PublicKey) UnmarshalText(in []byte) error {
75 | var tmp map[string][]byte
76 | var w, x, y curves.Point
77 |
78 | curve := curves.BLS12381G2()
79 | err := json.Unmarshal(in, &tmp)
80 | if err != nil {
81 | return err
82 | }
83 | if wBytes, ok := tmp["w"]; ok {
84 | w, err = curve.NewIdentityPoint().FromAffineCompressed(wBytes)
85 | if err != nil {
86 | return err
87 | }
88 | } else {
89 | return fmt.Errorf("missing expected map key 'w'")
90 | }
91 |
92 | if xBytes, ok := tmp["x"]; ok {
93 | x, err = curve.NewIdentityPoint().FromAffineCompressed(xBytes)
94 | if err != nil {
95 | return err
96 | }
97 | } else {
98 | return fmt.Errorf("missing expected map key 'x'")
99 | }
100 |
101 | if yBytes, ok := tmp["y"]; ok {
102 | y, err = curve.NewIdentityPoint().FromAffineCompressed(yBytes)
103 | if err != nil {
104 | return err
105 | }
106 | } else {
107 | return fmt.Errorf("missing expected map key 'y'")
108 | }
109 |
110 | W, _ := w.(*curves.PointBls12381G2)
111 | X, _ := x.(*curves.PointBls12381G2)
112 | Y, _ := y.(*curves.PointBls12381G2)
113 |
114 | goodW := !w.IsIdentity()
115 | goodX := !x.IsIdentity()
116 | goodY := !y.IsIdentity()
117 |
118 | if goodW && goodX && goodY {
119 | p.W = W
120 | p.X = X
121 | p.Y = Y
122 | return nil
123 | }
124 | return fmt.Errorf("invalid secret key")
125 | }
126 |
--------------------------------------------------------------------------------
/go/pkg/oberon/secret_key.go:
--------------------------------------------------------------------------------
1 | package oberon
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/coinbase/kryptology/pkg/core/curves"
7 | "github.com/coinbase/kryptology/pkg/core/curves/native"
8 | "github.com/coinbase/kryptology/pkg/core/curves/native/bls12381"
9 | "golang.org/x/crypto/sha3"
10 | "io"
11 | )
12 |
13 | type SecretKey struct {
14 | W *curves.ScalarBls12381
15 | X *curves.ScalarBls12381
16 | Y *curves.ScalarBls12381
17 | }
18 |
19 | func NewSecretKey(reader io.Reader) (*SecretKey, error) {
20 | curve := curves.BLS12381G2()
21 | w := curve.NewScalar().Random(reader)
22 | if w == nil {
23 | return nil, fmt.Errorf("unable to create secret key")
24 | }
25 | x := curve.NewScalar().Random(reader)
26 | if x == nil {
27 | return nil, fmt.Errorf("unable to create secret key")
28 | }
29 | y := curve.NewScalar().Random(reader)
30 | if y == nil {
31 | return nil, fmt.Errorf("unable to create secret key")
32 | }
33 | W, _ := w.(*curves.ScalarBls12381)
34 | X, _ := x.(*curves.ScalarBls12381)
35 | Y, _ := y.(*curves.ScalarBls12381)
36 | return &SecretKey{W, X, Y}, nil
37 | }
38 |
39 | func HashSecretKey(data []byte) (*SecretKey, error) {
40 | hasher := sha3.NewShake256()
41 | n, err := hasher.Write(toScalarDst)
42 | if err != nil {
43 | return nil, err
44 | }
45 | if n != len(toScalarDst) {
46 | return nil, fmt.Errorf("unable to write %d bytes", len(toScalarDst))
47 | }
48 | n, err = hasher.Write(data)
49 | if err != nil {
50 | return nil, err
51 | }
52 | if n != len(data) {
53 | return nil, fmt.Errorf("unable to write %d bytes", len(data))
54 | }
55 |
56 | var tmp [3]*curves.ScalarBls12381
57 | var scalar [48]byte
58 | for i := 0; i < 3; i++ {
59 | n, err = hasher.Read(scalar[:])
60 | if err != nil {
61 | return nil, err
62 | }
63 | if n != len(scalar) {
64 | return nil, fmt.Errorf("unable to write %d bytes", len(scalar))
65 | }
66 |
67 | tmp[i] = fromOkm(scalar[:])
68 | }
69 | return &SecretKey{
70 | W: tmp[0], X: tmp[1], Y: tmp[2],
71 | }, nil
72 | }
73 |
74 | func (s SecretKey) PublicKey() *PublicKey {
75 | pk := new(PublicKey)
76 | pk.FromSecretKey(&s)
77 | return pk
78 | }
79 |
80 | func (s *SecretKey) Sign(id []byte) (*Token, error) {
81 | t := new(Token)
82 | err := t.Create(s, id)
83 | if err != nil {
84 | return nil, err
85 | }
86 | return t, nil
87 | }
88 |
89 | func (s SecretKey) MarshalBinary() ([]byte, error) {
90 | w, x, y := s.W.Value.Bytes(), s.X.Value.Bytes(), s.Y.Value.Bytes()
91 | var tmp [96]byte
92 | copy(tmp[:32], w[:])
93 | copy(tmp[32:64], x[:])
94 | copy(tmp[64:96], y[:])
95 | return tmp[:], nil
96 | }
97 |
98 | func (s *SecretKey) UnmarshalBinary(in []byte) error {
99 | if len(in) != 96 {
100 | return fmt.Errorf("invalid length")
101 | }
102 | var t [native.FieldBytes]byte
103 | copy(t[:], in[:32])
104 | curve := curves.BLS12381G2()
105 | w, err := bls12381.Bls12381FqNew().SetBytes(&t)
106 | if err != nil {
107 | return err
108 | }
109 | copy(t[:], in[32:64])
110 | x, err := bls12381.Bls12381FqNew().SetBytes(&t)
111 | if err != nil {
112 | return err
113 | }
114 | copy(t[:], in[64:96])
115 | y, err := bls12381.Bls12381FqNew().SetBytes(&t)
116 | if err != nil {
117 | return err
118 | }
119 | if w.IsZero()|x.IsZero()|y.IsZero() == 1 {
120 | return fmt.Errorf("invalid secret key")
121 | }
122 | s.W, _ = curve.NewScalar().(*curves.ScalarBls12381)
123 | s.X, _ = curve.NewScalar().(*curves.ScalarBls12381)
124 | s.Y, _ = curve.NewScalar().(*curves.ScalarBls12381)
125 | s.W.Value = w
126 | s.X.Value = x
127 | s.Y.Value = y
128 | return nil
129 | }
130 |
131 | func (s SecretKey) MarshalText() ([]byte, error) {
132 | w, x, y := s.W.Value.Bytes(), s.X.Value.Bytes(), s.Y.Value.Bytes()
133 | tmp := map[string][]byte{
134 | "w": w[:],
135 | "x": x[:],
136 | "y": y[:],
137 | }
138 | return json.Marshal(&tmp)
139 | }
140 |
141 | func (s *SecretKey) UnmarshalText(in []byte) error {
142 | var tmp map[string][]byte
143 | var w, x, y *native.Field
144 | var t [native.FieldBytes]byte
145 |
146 | curve := curves.BLS12381G2()
147 | err := json.Unmarshal(in, &tmp)
148 | if err != nil {
149 | return err
150 | }
151 | if wBytes, ok := tmp["w"]; ok {
152 | if len(wBytes) != native.FieldBytes {
153 | return fmt.Errorf("invalid byte sequence")
154 | }
155 | copy(t[:], wBytes)
156 | w, err = bls12381.Bls12381FqNew().SetBytes(&t)
157 | if err != nil {
158 | return err
159 | }
160 | } else {
161 | return fmt.Errorf("missing expected map key 'w'")
162 | }
163 |
164 | if xBytes, ok := tmp["x"]; ok {
165 | if len(xBytes) != native.FieldBytes {
166 | return fmt.Errorf("invalid byte sequence")
167 | }
168 | copy(t[:], xBytes)
169 | x, err = bls12381.Bls12381FqNew().SetBytes(&t)
170 | if err != nil {
171 | return err
172 | }
173 | } else {
174 | return fmt.Errorf("missing expected map key 'x'")
175 | }
176 |
177 | if yBytes, ok := tmp["y"]; ok {
178 | if len(yBytes) != native.FieldBytes {
179 | return fmt.Errorf("invalid byte sequence")
180 | }
181 | copy(t[:], yBytes)
182 | y, err = bls12381.Bls12381FqNew().SetBytes(&t)
183 | if err != nil {
184 | return err
185 | }
186 | } else {
187 | return fmt.Errorf("missing expected map key 'y'")
188 | }
189 |
190 | if w.IsZero()|x.IsZero()|y.IsZero() == 1 {
191 | return fmt.Errorf("invalid secret key")
192 | }
193 |
194 | s.W, _ = curve.NewScalar().(*curves.ScalarBls12381)
195 | s.X, _ = curve.NewScalar().(*curves.ScalarBls12381)
196 | s.Y, _ = curve.NewScalar().(*curves.ScalarBls12381)
197 |
198 | s.W.Value = w
199 | s.X.Value = x
200 | s.Y.Value = y
201 |
202 | return nil
203 | }
204 |
--------------------------------------------------------------------------------
/go/pkg/oberon/secret_key_test.go:
--------------------------------------------------------------------------------
1 | package oberon
2 |
3 | import (
4 | "bytes"
5 | crand "crypto/rand"
6 | "github.com/stretchr/testify/require"
7 | "testing"
8 | )
9 |
10 | func TestSecretKey_MarshalBinary(t *testing.T) {
11 | testKey, err := NewSecretKey(crand.Reader)
12 | require.NoError(t, err)
13 | require.NotNil(t, testKey)
14 | tmp, err := testKey.MarshalBinary()
15 | require.NoError(t, err)
16 | require.NotNil(t, tmp)
17 | require.Equal(t, 96, len(tmp))
18 | testKey2 := new(SecretKey)
19 | err = testKey2.UnmarshalBinary(tmp)
20 | require.NoError(t, err)
21 | require.Equal(t, 1, testKey.W.Value.Equal(testKey2.W.Value))
22 | require.Equal(t, 1, testKey.X.Value.Equal(testKey2.X.Value))
23 | require.Equal(t, 1, testKey.Y.Value.Equal(testKey2.Y.Value))
24 | }
25 |
26 | func TestSecretKey_MarshalText(t *testing.T) {
27 | testKey, err := NewSecretKey(crand.Reader)
28 | require.NoError(t, err)
29 | require.NotNil(t, testKey)
30 | tmp, err := testKey.MarshalText()
31 | require.NoError(t, err)
32 | require.NotNil(t, tmp)
33 | require.Equal(t, 154, len(tmp))
34 | testKey2 := new(SecretKey)
35 | err = testKey2.UnmarshalText(tmp)
36 | require.NoError(t, err)
37 | require.Equal(t, 1, testKey.W.Value.Equal(testKey2.W.Value))
38 | require.Equal(t, 1, testKey.X.Value.Equal(testKey2.X.Value))
39 | require.Equal(t, 1, testKey.Y.Value.Equal(testKey2.Y.Value))
40 | }
41 |
42 | func TestSecretKey_UnmarshalBinary(t *testing.T) {
43 | testKey := []byte{231, 175, 93, 172, 172, 159, 138, 22, 143, 108, 240, 233, 143, 111, 174, 10, 175, 109, 4, 247, 202, 138, 82, 192, 16, 135, 104, 152, 34, 223, 148, 56, 171, 203, 127, 215, 175, 37, 230, 135, 18, 40, 163, 20, 220, 154, 2, 77, 244, 254, 17, 187, 49, 16, 249, 223, 214, 190, 126, 137, 233, 128, 214, 78, 41, 28, 173, 31, 93, 30, 96, 127, 135, 59, 35, 179, 112, 148, 8, 239, 135, 112, 86, 142, 60, 39, 40, 57, 115, 45, 197, 134, 142, 127, 0, 19}
44 | sk := new(SecretKey)
45 | err := sk.UnmarshalBinary(testKey)
46 | require.NoError(t, err)
47 | }
48 |
49 | func TestSecretKey_UnmarshalText(t *testing.T) {
50 | testKey := `{"w":[231,175,93,172,172,159,138,22,143,108,240,233,143,111,174,10,175,109,4,247,202,138,82,192,16,135,104,152,34,223,148,56],"x":[171,203,127,215,175,37,230,135,18,40,163,20,220,154,2,77,244,254,17,187,49,16,249,223,214,190,126,137,233,128,214,78],"y":[41,28,173,31,93,30,96,127,135,59,35,179,112,148,8,239,135,112,86,142,60,39,40,57,115,45,197,134,142,127,0,19]}`
51 | sk := new(SecretKey)
52 | err := sk.UnmarshalText([]byte(testKey))
53 | require.NoError(t, err)
54 | }
55 |
56 | func TestHashSecretKey(t *testing.T) {
57 | var seed [32]byte
58 | sk, err := HashSecretKey(seed[:])
59 | require.NoError(t, err)
60 | out, err := sk.MarshalBinary()
61 | require.NoError(t, err)
62 | expBytes := []byte{132, 134, 52, 39, 104, 194, 25, 230, 216, 42, 0, 63, 34, 54, 107, 231, 82, 166, 247, 224, 33, 36, 218, 239, 81, 144, 152, 175, 106, 143, 129, 6, 122, 192, 119, 97, 255, 119, 247, 135, 208, 177, 81, 210, 179, 111, 141, 72, 32, 87, 81, 207, 39, 39, 24, 91, 125, 206, 144, 53, 124, 67, 100, 86, 107, 124, 135, 14, 83, 6, 145, 211, 0, 7, 82, 131, 174, 107, 136, 56, 235, 54, 81, 165, 243, 235, 216, 215, 95, 164, 31, 48, 118, 118, 67, 37}
63 | require.Equal(t, 0, bytes.Compare(out, expBytes))
64 | }
65 |
--------------------------------------------------------------------------------
/go/pkg/oberon/token.go:
--------------------------------------------------------------------------------
1 | package oberon
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/coinbase/kryptology/pkg/core/curves"
7 | "github.com/coinbase/kryptology/pkg/core/curves/native/bls12381"
8 | )
9 |
10 | type Token struct {
11 | Value *curves.PointBls12381G1
12 | }
13 |
14 | func NewToken(sk *SecretKey, id []byte) (*Token, error) {
15 | t := new(Token)
16 | err := t.Create(sk, id)
17 | return t, err
18 | }
19 |
20 | // Create a new token
21 | func (t *Token) Create(sk *SecretKey, id []byte) error {
22 | m, err := computeM(id)
23 | if err != nil {
24 | return err
25 | }
26 | mTick, err := computeMTick(m)
27 | if err != nil {
28 | return err
29 | }
30 | u, err := computeU(mTick)
31 | if err != nil {
32 | return err
33 | }
34 |
35 | tv1 := sk.W.Mul(mTick)
36 | tv2 := sk.Y.Mul(m)
37 | e := sk.X.Add(tv1).Add(tv2)
38 |
39 | // u * (x + w * m' + y * m)
40 | sigma := u.Mul(e).(*curves.PointBls12381G1)
41 |
42 | if !isValidPointG1(sigma) {
43 | return fmt.Errorf("invalid token")
44 | }
45 | t.Value = sigma
46 | return nil
47 | }
48 |
49 | // Verify whether the token is valid to the public key
50 | func (t *Token) Verify(pk *PublicKey, id []byte) error {
51 | m, err := computeM(id)
52 | if err != nil {
53 | return err
54 | }
55 | mTick, err := computeMTick(m)
56 | if err != nil {
57 | return err
58 | }
59 |
60 | u, err := computeU(mTick)
61 | if err != nil {
62 | return err
63 | }
64 | curve := curves.BLS12381G2()
65 | rhs := curve.NewIdentityPoint().SumOfProducts([]curves.Point{pk.W, pk.X, pk.Y}, []curves.Scalar{mTick, curve.NewScalar().One(), m}).(*curves.PointBls12381G2)
66 | g := curve.NewGeneratorPoint().(*curves.PointBls12381G2)
67 |
68 | engine := new(bls12381.Engine)
69 | engine.AddPairInvG1(u.Value, rhs.Value)
70 | engine.AddPair(t.Value.Value, g.Value)
71 | if engine.Check() {
72 | return nil
73 | } else {
74 | return fmt.Errorf("check failed")
75 | }
76 | }
77 |
78 | func (t *Token) ApplyBlinding(token *Token, b *Blinding) *Token {
79 | t.Value, _ = token.Value.Sub(b.Value).(*curves.PointBls12381G1)
80 | return t
81 | }
82 |
83 | func (t *Token) RemoveBlinding(token *Token, b *Blinding) *Token {
84 | t.Value, _ = token.Value.Add(b.Value).(*curves.PointBls12381G1)
85 | return t
86 | }
87 |
88 | func (t Token) MarshalBinary() ([]byte, error) {
89 | return t.Value.ToAffineCompressed(), nil
90 | }
91 |
92 | func (t *Token) UnmarshalBinary(data []byte) error {
93 | tt, err := t.Value.FromAffineCompressed(data)
94 | if err != nil {
95 | return err
96 | }
97 | t.Value = tt.(*curves.PointBls12381G1)
98 | return nil
99 | }
100 |
101 | func (t Token) MarshalText() ([]byte, error) {
102 | return json.Marshal(t.Value.ToAffineCompressed())
103 | }
104 |
105 | func (t *Token) UnmarshalText(in []byte) error {
106 | var data [48]byte
107 | err := json.Unmarshal(in, &data)
108 | if err != nil {
109 | return err
110 | }
111 | return t.UnmarshalBinary(data[:])
112 | }
113 |
--------------------------------------------------------------------------------
/go/pkg/oberon/token_test.go:
--------------------------------------------------------------------------------
1 | package oberon
2 |
3 | import (
4 | "github.com/stretchr/testify/require"
5 | "testing"
6 | )
7 |
8 | func TestValidToken(t *testing.T) {
9 | skBytes := []byte{180, 92, 239, 44, 240, 143, 149, 163, 45, 177, 22, 179, 146, 120, 129, 229, 78, 56, 70, 205, 251, 160, 140, 79, 159, 138, 6, 56, 250, 236, 176, 11, 70, 53, 138, 199, 245, 180, 223, 213, 128, 166, 122, 225, 67, 58, 138, 201, 19, 114, 57, 149, 70, 141, 31, 45, 180, 30, 208, 222, 234, 112, 21, 34, 37, 5, 163, 172, 96, 40, 81, 27, 89, 86, 163, 93, 15, 201, 200, 183, 157, 18, 134, 140, 156, 43, 79, 231, 42, 234, 198, 139, 130, 52, 176, 106}
10 | sk := new(SecretKey)
11 | err := sk.UnmarshalBinary(skBytes)
12 | require.NoError(t, err)
13 | pk := sk.PublicKey()
14 |
15 | expToken := []byte{
16 | 174, 221, 77, 7, 147, 66, 236, 180, 112, 106, 14, 104, 35, 123, 13, 189, 211, 158, 32, 194,
17 | 24, 50, 49, 93, 87, 126, 102, 20, 192, 132, 157, 221, 83, 98, 81, 93, 155, 137, 134, 9, 58,
18 | 108, 30, 237, 108, 13, 40, 242,
19 | }
20 | token, err := sk.Sign(testId)
21 | require.NoError(t, err)
22 | require.Equal(t, expToken, token.Value.ToAffineCompressed())
23 | require.NoError(t, token.Verify(pk, testId))
24 | require.Error(t, token.Verify(pk, []byte("wrong identity")))
25 | }
26 |
--------------------------------------------------------------------------------
/go/pkg/oberon/util.go:
--------------------------------------------------------------------------------
1 | package oberon
2 |
3 | import (
4 | "fmt"
5 | "github.com/coinbase/kryptology/pkg/core/curves"
6 | "github.com/coinbase/kryptology/pkg/core/curves/native"
7 | "github.com/coinbase/kryptology/pkg/core/curves/native/bls12381"
8 | "golang.org/x/crypto/sha3"
9 | )
10 |
11 | var (
12 | toScalarDst = []byte("OBERON_BLS12381FQ_XOF:SHAKE-256_")
13 | toCurveDst = []byte("OBERON_BLS12381G1_XOF:SHAKE-256_SSWU_RO_")
14 | )
15 |
16 | func computeM(id []byte) (*curves.ScalarBls12381, error) {
17 | m, err := hashToScalar([][]byte{id})
18 | if err != nil {
19 | return nil, err
20 | }
21 | if m.IsZero() {
22 | return nil, fmt.Errorf("m == 0")
23 | }
24 | return m, nil
25 | }
26 |
27 | func computeMTick(m *curves.ScalarBls12381) (*curves.ScalarBls12381, error) {
28 | mTick, err := hashToScalar([][]byte{reverseBytes(m.Bytes())})
29 | if err != nil {
30 | return nil, err
31 | }
32 | if mTick.IsZero() {
33 | return nil, fmt.Errorf("m' == 0")
34 | }
35 | return mTick, nil
36 | }
37 |
38 | func computeU(mTick *curves.ScalarBls12381) (*curves.PointBls12381G1, error) {
39 | u, err := hashToCurve(reverseBytes(mTick.Bytes()))
40 | if err != nil {
41 | return nil, err
42 | }
43 | if !isValidPointG1(u) {
44 | return nil, fmt.Errorf("invalid u")
45 | }
46 | return u, nil
47 | }
48 |
49 | func hashToCurve(data []byte) (*curves.PointBls12381G1, error) {
50 | pt := curves.BLS12381G1().NewIdentityPoint().(*curves.PointBls12381G1)
51 | pt.Value = new(bls12381.G1).Hash(native.EllipticPointHasherShake256(), data, toCurveDst)
52 | return pt, nil
53 | }
54 |
55 | func hashToScalar(data [][]byte) (*curves.ScalarBls12381, error) {
56 | hasher := sha3.NewShake256()
57 | n, err := hasher.Write(toScalarDst)
58 | if err != nil {
59 | return nil, err
60 | }
61 | if n != len(toScalarDst) {
62 | return nil, fmt.Errorf("unable to write %d bytes", len(toScalarDst))
63 | }
64 | for _, d := range data {
65 | n, err = hasher.Write(d)
66 | if err != nil {
67 | return nil, err
68 | }
69 | if n != len(d) {
70 | return nil, fmt.Errorf("unable to write %d bytes", len(data))
71 | }
72 | }
73 | var scalar [48]byte
74 | n, err = hasher.Read(scalar[:])
75 | if err != nil {
76 | return nil, err
77 | }
78 | if n != len(scalar) {
79 | return nil, fmt.Errorf("unable to write %d bytes", len(scalar))
80 | }
81 |
82 | return fromOkm(scalar[:]), nil
83 | }
84 |
85 | func fromOkm(scalar []byte) *curves.ScalarBls12381 {
86 | var t [64]byte
87 | copy(t[:48], reverseBytes(scalar))
88 | value := bls12381.Bls12381FqNew().SetBytesWide(&t)
89 | sc := curves.BLS12381G1().NewScalar().(*curves.ScalarBls12381)
90 | sc.Value = value
91 | return sc
92 | }
93 |
94 | func reverseBytes(in []byte) []byte {
95 | out := make([]byte, len(in))
96 |
97 | for i, j := 0, len(in)-1; j >= 0; i, j = i+1, j-1 {
98 | out[i] = in[j]
99 | }
100 |
101 | return out
102 | }
103 |
104 | func isValidPointG1(p *curves.PointBls12381G1) bool {
105 | id := p.Value.IsIdentity()
106 | // if id == 1 then t == 0
107 | // if id == 0 then t == 1
108 | t := -id + 1
109 | return p.Value.IsOnCurve()&p.Value.InCorrectSubgroup()&t == 1
110 | }
111 |
112 | func isValidPointG2(p *curves.PointBls12381G2) bool {
113 | id := p.Value.IsIdentity()
114 | // if id == 1 then t == 0
115 | // if id == 0 then t == 1
116 | t := -id + 1
117 | return p.Value.IsOnCurve()&p.Value.InCorrectSubgroup()&t == 1
118 | }
119 |
--------------------------------------------------------------------------------
/go/pkg/oberon/util_test.go:
--------------------------------------------------------------------------------
1 | package oberon
2 |
3 | import (
4 | "bytes"
5 | "github.com/stretchr/testify/require"
6 | "testing"
7 | )
8 |
9 | func TestHashToCurve(t *testing.T) {
10 | testVectors := [][]byte{
11 | bytes.Repeat([]byte{0}, 16),
12 | bytes.Repeat([]byte{1}, 16),
13 | bytes.Repeat([]byte{2}, 16),
14 | }
15 | testExp := [][]byte{
16 | {172, 138, 253, 253, 253, 194, 82, 104, 59, 43, 38, 180, 176, 186, 155, 12, 51, 128, 241, 15, 122, 17, 6, 240, 63, 239, 30, 31, 84, 126, 192, 19, 97, 7, 220, 175, 181, 204, 66, 68, 85, 233, 178, 62, 125, 74, 138, 35},
17 | {177, 41, 50, 159, 138, 238, 193, 134, 118, 241, 151, 69, 210, 132, 242, 141, 205, 137, 44, 226, 1, 197, 170, 198, 129, 153, 21, 149, 108, 135, 129, 252, 119, 198, 71, 58, 101, 136, 252, 113, 190, 70, 167, 165, 160, 238, 128, 255},
18 | {133, 29, 101, 21, 224, 217, 72, 134, 140, 38, 141, 32, 109, 145, 150, 252, 67, 150, 121, 117, 158, 12, 192, 250, 121, 159, 163, 42, 217, 239, 254, 95, 180, 165, 25, 45, 14, 153, 122, 129, 14, 49, 229, 17, 230, 193, 218, 69},
19 | }
20 |
21 | for i, v := range testVectors {
22 | p, err := hashToCurve(v)
23 | require.NoError(t, err)
24 | require.Equal(t, testExp[i], p.ToAffineCompressed())
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/img/one-pass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cryptidtech/oberon/538d549fa58037f2a65d5f31df66520e212948a7/img/one-pass.png
--------------------------------------------------------------------------------
/img/three-pass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cryptidtech/oberon/538d549fa58037f2a65d5f31df66520e212948a7/img/three-pass.png
--------------------------------------------------------------------------------
/include/oberon.h:
--------------------------------------------------------------------------------
1 | #ifndef __oberon__included__
2 | #define __oberon__included__
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | typedef struct ByteBuffer {
10 | int64_t len;
11 | uint8_t *data;
12 | } ByteBuffer;
13 |
14 | typedef struct ByteArray {
15 | uintptr_t length;
16 | const uint8_t *data;
17 | } ByteArray;
18 |
19 | typedef struct ExternError {
20 | int32_t code;
21 | char* message;
22 | } ExternError;
23 |
24 | void oberon_string_free(char *s);
25 | void oberon_byte_buffer_free(struct ByteBuffer v);
26 | void oberon_create_proof_free(uint64_t handle, struct ExternError *err);
27 |
28 | int32_t oberon_secret_key_size(void);
29 | int32_t oberon_public_key_size(void);
30 | int32_t oberon_token_size(void);
31 | int32_t oberon_blinding_size(void);
32 | int32_t oberon_proof_size(void);
33 |
34 | int32_t oberon_new_secret_key(struct ByteBuffer secret_key);
35 | int32_t oberon_get_public_key(struct ByteArray secret_key, struct ByteBuffer *public_key, struct ExternError *err);
36 | int32_t oberon_secret_key_from_seed(struct ByteArray seed, struct ByteBuffer *secret_key);
37 | int32_t oberon_new_token(struct ByteArray secret_key, struct ByteArray id, struct ByteBuffer *token, struct ExternError *err);
38 | int32_t oberon_verify_token(struct ByteArray token, struct ByteArray public_key, struct ByteArray id, struct ExternError *err);
39 | int32_t oberon_create_blinding(struct ByteArray data, struct ByteBuffer *blinding);
40 | int32_t oberon_add_blinding(struct ByteArray old_token, struct ByteArray data, struct ByteBuffer *new_token, struct ExternError *err);
41 | int32_t oberon_remove_blinding(struct ByteArray old_token, struct ByteArray data, struct ByteBuffer *new_token, struct ExternError *err);
42 | uint64_t oberon_create_proof_init(struct ExternError *err);
43 | int32_t oberon_create_proof_set_token(uint64_t handle, struct ByteArray token, struct ExternError *err);
44 | int32_t oberon_create_proof_set_id(uint64_t handle, struct ByteArray id, struct ExternError *err);
45 | int32_t oberon_create_proof_set_nonce(uint64_t handle, struct ByteArray nonce, struct ExternError *err);
46 | int32_t oberon_create_proof_add_blinding(uint64_t handle, struct ByteArray blinding, struct ExternError *err);
47 | int32_t oberon_create_proof_finish(uint64_t handle, struct ByteBuffer *proof, struct ExternError *err);
48 | int32_t oberon_verify_proof(struct ByteArray proof, struct ByteArray public_key, struct ByteArray id, struct ByteArray nonce, struct ExternError *err);
49 |
50 | #endif
--------------------------------------------------------------------------------
/nodejs/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | index.node
3 | **/node_modules
4 | **/.DS_Store
5 | npm-debug.log*
6 | package-lock.json
7 |
8 |
--------------------------------------------------------------------------------
/nodejs/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "nodeoberon"
3 | version = "2.0.0"
4 | description = "A succinct ZKP protocol for authentication"
5 | authors = ["Michael Lodder "]
6 | license = "Apache-2.0"
7 | edition = "2021"
8 | readme = "README.md"
9 | exclude = ["index.node"]
10 |
11 | [lib]
12 | crate-type = ["cdylib"]
13 |
14 | [dependencies]
15 | oberon = { path = "../rust" }
16 | rand = "0.8"
17 |
18 | [dependencies.neon]
19 | version = "0.10"
20 | default-features = false
21 | features = ["napi-6"]
22 |
--------------------------------------------------------------------------------
/nodejs/LICENSE:
--------------------------------------------------------------------------------
1 | ../LICENSE
--------------------------------------------------------------------------------
/nodejs/README.md:
--------------------------------------------------------------------------------
1 | ## Build Instructions
2 |
3 | ### Amazon Linux 2
4 | 1. yum update
5 | 2. yum install tar gzip gcc git
6 | 3. curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
7 | 4. source "$HOME/.cargo/env"
8 | 5. curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
9 | 6. . ~/.nvm/nvm.sh
10 | 7. nvm install --lts
11 | 8. npm install -g npm@9.5.0
12 | 9. git clone https://github.com/cryptidtech/oberon.git
13 | 10. cd oberon/nodejs
14 | 11. npm install
15 |
16 | ### Ubuntu
17 | 1. apt update
18 | 2. apt upgrade -y
19 | 3. apt install build-essential pkg-config clang
20 | 4. curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
21 | 5. source "$HOME/.cargo/env"
22 | 6. curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
23 | 7. . ~/.nvm/nvm.sh
24 | 8. nvm install --lts
25 | 9. npm install -g npm@9.5.0
26 | 10. git clone https://github.com/cryptidtech/oberon.git
27 | 11. cd oberon/nodejs
28 | 12. npm install
29 |
--------------------------------------------------------------------------------
/nodejs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nodeoberon",
3 | "version": "1.1.1",
4 | "description": "A succinct ZKP protocol for authentication",
5 | "main": "index.node",
6 | "scripts": {
7 | "build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
8 | "build-debug": "npm run build --",
9 | "build-release": "npm run build -- --release",
10 | "install": "npm run build-release",
11 | "test": "cargo test"
12 | },
13 | "author": "Michael Lodder ",
14 | "license": "Apache-2.0",
15 | "devDependencies": {
16 | "cargo-cp-artifact": "^0.1"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/cryptidtech/oberon.git"
21 | },
22 | "bugs": {
23 | "url": "https://github.com/cryptidtech/oberon/issues"
24 | },
25 | "homepage": "https://github.com/cryptidtech/oberon#readme"
26 | }
27 |
--------------------------------------------------------------------------------
/nodejs/src/lib.rs:
--------------------------------------------------------------------------------
1 | use neon::{
2 | prelude::*,
3 | types::buffer::TypedArray,
4 | };
5 | use oberon::*;
6 | use std::convert::TryFrom;
7 |
8 | macro_rules! slice_to_js_array_buffer {
9 | ($slice:expr, $cx:expr) => {{
10 | let mut result = JsArrayBuffer::new(&mut $cx, $slice.len())?;
11 | result.as_mut_slice(&mut $cx).copy_from_slice($slice);
12 | result
13 | }};
14 | }
15 |
16 | macro_rules! obj_field_to_vec {
17 | ($cx:expr, $field: expr) => {{
18 | let v: Vec> = $cx
19 | .argument::($field)?
20 | .to_vec(&mut $cx)?;
21 | v
22 | }};
23 | }
24 |
25 |
26 | /// @returns ArrayBuffer secret key
27 | fn new_secret_key(mut cx: FunctionContext) -> JsResult {
28 | let sk = SecretKey::new(rand::thread_rng());
29 | Ok(slice_to_js_array_buffer!(&sk.to_bytes(), cx))
30 | }
31 |
32 |
33 | /// @param ArrayBuffer secret key
34 | /// @returns ArrayBuffer public key
35 | fn get_public_key(mut cx: FunctionContext) -> JsResult {
36 | let ja = JsArrayBuffer::new(&mut cx, 0)?;
37 | let secret_key_handle: Handle = cx.argument(0)?;
38 | let secret_key_data = secret_key_handle.as_slice(&cx);
39 | if secret_key_data.len() != SecretKey::BYTES {
40 | return cx.throw(ja);
41 | }
42 | let secret_key_bytes = <[u8; SecretKey::BYTES]>::try_from(secret_key_data).unwrap();
43 | let ct_sk = SecretKey::from_bytes(&secret_key_bytes);
44 | if ct_sk.is_some().unwrap_u8() == 1u8 {
45 | let sk = ct_sk.unwrap();
46 | let pk = PublicKey::from(&sk);
47 | Ok(slice_to_js_array_buffer!(&pk.to_bytes(), cx))
48 | } else {
49 | cx.throw(ja)
50 | }
51 | }
52 |
53 |
54 | /// @param ArrayBuffer `seed` - seed data
55 | /// @returns ArrayBuffer `secret key` - the secret key
56 | fn secret_key_from_seed(mut cx: FunctionContext) -> JsResult {
57 | let seed_handle: Handle = cx.argument(0)?;
58 | let seed_data = seed_handle.as_slice(&cx);
59 | let sk = SecretKey::hash(seed_data);
60 | Ok(slice_to_js_array_buffer!(&sk.to_bytes(), cx))
61 | }
62 |
63 |
64 | /// @param ArrayBuffer `id` - The identifier to use for this token
65 | /// @param ArrayBuffer `secretKey` - The secret key used for signing this token
66 | /// @returns ArrayBuffer `token` - The token
67 | fn new_token(mut cx: FunctionContext) -> JsResult {
68 | let ja = JsArrayBuffer::new(&mut cx, 0)?;
69 | let id_buffer: Handle = cx.argument(1)?;
70 | let sk_buffer: Handle = cx.argument(0)?;
71 |
72 | let id_bytes = id_buffer.as_slice(&cx);
73 | let sk_bytes = sk_buffer.as_slice(&cx);
74 |
75 | if sk_bytes.len() != SecretKey::BYTES {
76 | return cx.throw(ja);
77 | }
78 |
79 | let sk = SecretKey::from_bytes(&<[u8; SecretKey::BYTES]>::try_from(sk_bytes).unwrap()).unwrap();
80 | match Token::new(&sk, id_bytes) {
81 | None => {
82 | let out = slice_to_js_array_buffer!(String::from("failed to create the token").as_bytes(), cx);
83 | cx.throw(out)
84 | },
85 | Some(token) => Ok(slice_to_js_array_buffer!(&token.to_bytes(), cx)),
86 | }
87 | }
88 |
89 |
90 | /// @param ArrayBuffer `token` - The token
91 | /// @param ArrayBuffer `publicKey` - The public key used for verifying this token
92 | /// @param ArrayBuffer `id` - The identifier to use for this token
93 | /// @returns bool - True if valid, false otherwise
94 | fn verify_token(mut cx: FunctionContext) -> JsResult {
95 | let res = JsBoolean::new(&mut cx, false);
96 | let id_buffer: Handle = cx.argument(2)?;
97 | let pk_buffer: Handle = cx.argument(1)?;
98 | let tk_buffer: Handle = cx.argument(0)?;
99 |
100 | let id_bytes = id_buffer.as_slice(&cx);
101 | let pk_bytes = pk_buffer.as_slice(&cx);
102 | let tk_bytes = tk_buffer.as_slice(&cx);
103 |
104 | if pk_bytes.len() != PublicKey::BYTES {
105 | return cx.throw(res);
106 | }
107 | if tk_bytes.len() != Token::BYTES {
108 | return cx.throw(res);
109 | }
110 |
111 | let mut pk_size = [0u8; PublicKey::BYTES];
112 | pk_size.copy_from_slice(pk_bytes);
113 | let tk_size = <[u8; Token::BYTES]>::try_from(tk_bytes).unwrap();
114 |
115 | let ct_pk = PublicKey::from_bytes(&pk_size);
116 | let ct_tk = Token::from_bytes(&tk_size);
117 |
118 | match (ct_pk.is_some().unwrap_u8(), ct_tk.is_some().unwrap_u8()) {
119 | (1u8, 1u8) => {
120 | let pk = ct_pk.unwrap();
121 | let tk = ct_tk.unwrap();
122 |
123 | let r = pk.verify_token(id_bytes, &tk).unwrap_u8() == 1u8;
124 |
125 | Ok(JsBoolean::new(&mut cx, r))
126 | },
127 | (_, _) => {
128 | return cx.throw(res);
129 | }
130 | }
131 | }
132 |
133 |
134 | /// @param ArrayBuffer `token` - The token
135 | /// @param ArrayBuffer `blinding_factor` - The blinding factor
136 | /// @returns ArrayBuffer `new_token` -The token with the blinding factor applied
137 | fn add_blinding(mut cx: FunctionContext) -> JsResult {
138 | let ja = JsArrayBuffer::new(&mut cx, 0)?;
139 | let blinding_buffer: Handle = cx.argument(1)?;
140 | let tk_buffer: Handle = cx.argument(0)?;
141 |
142 | let blinding_bytes = blinding_buffer.as_slice(&cx);
143 | let tk_bytes = tk_buffer.as_slice(&cx);
144 |
145 | if tk_bytes.len() != Token::BYTES {
146 | return cx.throw(ja);
147 | }
148 | let tk_size = <[u8; Token::BYTES]>::try_from(tk_bytes).unwrap();
149 |
150 | let ct_tk = Token::from_bytes(&tk_size);
151 |
152 | if ct_tk.is_none().unwrap_u8() == 1u8 {
153 | return cx.throw(ja);
154 | }
155 | let tk = ct_tk.unwrap();
156 |
157 | let new_tk = tk - Blinding::new(blinding_bytes);
158 | Ok(slice_to_js_array_buffer!(&new_tk.to_bytes(), cx))
159 | }
160 |
161 |
162 | /// @param ArrayBuffer `token` - The token
163 | /// @param ArrayBuffer `blinding_factor` - The blinding factor
164 | /// @returns ArrayBuffer `new_token` -The token with the blinding factor removed
165 | fn remove_blinding(mut cx: FunctionContext) -> JsResult {
166 | let ja = JsArrayBuffer::new(&mut cx, 0)?;
167 |
168 | let blinding_buffer: Handle = cx.argument(1)?;
169 | let tk_buffer: Handle = cx.argument(0)?;
170 |
171 | let blinding_bytes = blinding_buffer.as_slice(&cx);
172 | let tk_bytes = tk_buffer.as_slice(&cx);
173 |
174 | if tk_bytes.len() != Token::BYTES {
175 | return cx.throw(ja);
176 | }
177 | let tk_size = <[u8; Token::BYTES]>::try_from(tk_bytes).unwrap();
178 |
179 | let ct_tk = Token::from_bytes(&tk_size);
180 |
181 | if ct_tk.is_none().unwrap_u8() == 1u8 {
182 | return cx.throw(ja);
183 | }
184 | let tk = ct_tk.unwrap();
185 |
186 | let new_tk = tk + Blinding::new(blinding_bytes);
187 | Ok(slice_to_js_array_buffer!(&new_tk.to_bytes(), cx))
188 | }
189 |
190 |
191 | /// @param ArrayBuffer `token` - The token
192 | /// @param ArrayBuffer `id` - The identifier
193 | /// @param Array `blindings` - The blinding factors
194 | /// @param ArrayBuffer `nonce` - The proof nonce
195 | fn create_proof(mut cx: FunctionContext) -> JsResult {
196 | let ja = JsArrayBuffer::new(&mut cx, 0)?;
197 |
198 | let tk_buffer: Handle = cx.argument(0)?;
199 | let id_buffer: Handle = cx.argument(1)?;
200 | let blinding_factor_buffer: Vec> = obj_field_to_vec!(cx, 2);
201 | let nonce_buffer: Handle = cx.argument(3)?;
202 |
203 | let tk_bytes = tk_buffer.as_slice(&cx);
204 | let id = id_buffer.as_slice(&cx).to_vec();
205 | let nonce = nonce_buffer.as_slice(&cx).to_vec();
206 |
207 | if tk_bytes.len() != Token::BYTES {
208 | return cx.throw(ja);
209 | }
210 |
211 | let tk_size = <[u8; Token::BYTES]>::try_from(tk_bytes).unwrap();
212 |
213 | let ct_tk = Token::from_bytes(&tk_size);
214 |
215 | if ct_tk.is_none().unwrap_u8() == 1u8 {
216 | return cx.throw(ja);
217 | }
218 | let mut bs = Vec::new();
219 | for b in &blinding_factor_buffer {
220 | let a: Handle = b.downcast_or_throw(&mut cx)?;
221 | let blinding = Blinding::new(a.as_slice(&cx));
222 | bs.push(blinding);
223 | }
224 |
225 | let rng = rand::thread_rng();
226 | let token = ct_tk.unwrap();
227 | match Proof::new(&token, &bs, &id, &nonce, rng) {
228 | None => cx.throw(ja),
229 | Some(proof) => Ok(slice_to_js_array_buffer!(&proof.to_bytes(), cx))
230 | }
231 | }
232 |
233 |
234 | /// @param ArrayBuffer `proof` - The proof
235 | /// @param ArrayBuffer `public_key` - The verification key
236 | /// @param ArrayBuffer `id` - The identifier
237 | /// @param ArrayBuffer `nonce` - The proof nonce
238 | /// @returns True if valid, false otherwise
239 | fn verify_proof(mut cx: FunctionContext) -> JsResult {
240 | let f = JsBoolean::new(&mut cx, false);
241 |
242 | let proof_buffer: Handle = cx.argument(0)?;
243 | let public_key_buffer: Handle = cx.argument(1)?;
244 | let id_buffer: Handle = cx.argument(2)?;
245 | let nonce_buffer: Handle = cx.argument(3)?;
246 |
247 | let pf_bytes = proof_buffer.as_slice(&cx);
248 | let pk_bytes = public_key_buffer.as_slice(&cx);
249 | let id = id_buffer.as_slice(&cx);
250 | let nonce = nonce_buffer.as_slice(&cx);
251 |
252 | if pk_bytes.len() != PublicKey::BYTES {
253 | return cx.throw(f);
254 | }
255 | if pf_bytes.len() != Proof::BYTES {
256 | return cx.throw(f);
257 | }
258 | let mut pk_size = [0u8; PublicKey::BYTES];
259 | pk_size.copy_from_slice(pk_bytes);
260 |
261 | let mut pf_size = [0u8; Proof::BYTES];
262 | pf_size.copy_from_slice(pf_bytes);
263 |
264 | let ct_pk = PublicKey::from_bytes(&pk_size);
265 | let ct_pf = Proof::from_bytes(&pf_size);
266 |
267 | match (ct_pk.is_some().unwrap_u8(), ct_pf.is_some().unwrap_u8()) {
268 | (1u8, 1u8) => {
269 | let pubkey = ct_pk.unwrap();
270 | let proof = ct_pf.unwrap();
271 |
272 | let res = proof.open(pubkey, id, nonce).unwrap_u8() == 1;
273 | Ok(JsBoolean::new(&mut cx, res))
274 | },
275 | (_, _) => cx.throw(f)
276 | }
277 | }
278 |
279 | #[neon::main]
280 | fn main(mut cx: ModuleContext) -> NeonResult<()> {
281 | cx.export_function("newSecretKey", new_secret_key)?;
282 | cx.export_function("getPublicKey", get_public_key)?;
283 | cx.export_function("secretKeyFromSeed", secret_key_from_seed)?;
284 | cx.export_function("newToken", new_token)?;
285 | cx.export_function("verifyToken", verify_token)?;
286 | cx.export_function("addBlinding", add_blinding)?;
287 | cx.export_function("removeBlinding", remove_blinding)?;
288 | cx.export_function("createProof", create_proof)?;
289 | cx.export_function("verifyProof", verify_proof)?;
290 | Ok(())
291 | }
292 |
293 | // register_module!(mut cx, {
294 | // cx.export_function("newSecretKey", new_secret_key)?;
295 | // cx.export_function("getPublicKey", get_public_key)?;
296 | // cx.export_function("secretKeyFromSeed", secret_key_from_seed)?;
297 | // cx.export_function("newToken", new_token)?;
298 | // cx.export_function("verifyToken", verify_token)?;
299 | // cx.export_function("addBlinding", add_blinding)?;
300 | // cx.export_function("removeBlinding", remove_blinding)?;
301 | // cx.export_function("createProof", create_proof)?;
302 | // cx.export_function("verifyProof", verify_proof)?;
303 | // Ok(())
304 | // });
305 |
306 |
--------------------------------------------------------------------------------
/python/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | dist
3 | .pytest_cache
4 | __pycache__
5 | *.dll
6 | *.dylib
7 | *.so
8 | *.egg-info
9 |
--------------------------------------------------------------------------------
/python/LICENSE:
--------------------------------------------------------------------------------
1 | ../LICENSE
--------------------------------------------------------------------------------
/python/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | python3 -m build
4 | python3 python3 -m twine upload --repository pypi dist/*
5 |
--------------------------------------------------------------------------------
/python/oberon/__init__.py:
--------------------------------------------------------------------------------
1 | """Oberon Python wrapper library"""
2 |
3 | from .classes import SecretKey, PublicKey, Token, Proof
4 |
5 | __all__ = [
6 | "SecretKey",
7 | "PublicKey",
8 | "Token",
9 | "Proof"
10 | ]
11 |
12 |
--------------------------------------------------------------------------------
/python/oberon/bindings.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pdb
4 | import os
5 | import sys
6 | from ctypes import (
7 | CDLL,
8 | POINTER,
9 | Structure,
10 | byref,
11 | string_at,
12 | c_char_p,
13 | c_int32,
14 | c_int64,
15 | c_uint64,
16 | c_ubyte,
17 | )
18 |
19 | from ctypes.util import find_library
20 | from typing import Optional, Union
21 |
22 | LIB: CDLL = None
23 |
24 | class FfiByteBuffer(Structure):
25 | """A byte buffer allocated by python."""
26 | _fields_ = [
27 | ("length", c_int64),
28 | ("data", POINTER(c_ubyte)),
29 | ]
30 |
31 |
32 | class FfiError(Structure):
33 | """An error allocated by python."""
34 | _fields_ = [
35 | ("code", c_int32),
36 | ("message", c_char_p),
37 | ]
38 |
39 |
40 | def _decode_bytes(arg: Optional[Union[str, bytes, FfiByteBuffer]]) -> bytes:
41 | if isinstance(arg, FfiByteBuffer):
42 | return string_at(arg.data, arg.length)
43 | if isinstance(arg, memoryview):
44 | return string_at(arg.obj, arg.nbytes)
45 | if isinstance(arg, bytearray):
46 | return arg
47 | if arg is not None:
48 | if isinstance(arg, str):
49 | return arg.encode("utf-8")
50 | return bytearray()
51 |
52 |
53 | def _encode_bytes(arg: Optional[Union[str, bytes, FfiByteBuffer]]) -> FfiByteBuffer:
54 | if isinstance(arg, FfiByteBuffer):
55 | return arg
56 | buf = FfiByteBuffer()
57 | if isinstance(arg, memoryview):
58 | buf.length = arg.nbytes
59 | if arg.contiguous and not arg.readonly:
60 | buf.data = (c_ubyte * buf.length).from_buffer(arg.obj)
61 | else:
62 | buf.data = (c_ubyte * buf.length).from_buffer_copy(arg.obj)
63 | elif isinstance(arg, bytearray):
64 | buf.length = len(arg)
65 | if buf.length > 0:
66 | buf.data = (c_ubyte * buf.length).from_buffer(arg)
67 | elif arg is not None:
68 | if isinstance(arg, str):
69 | arg = arg.encode("utf-8")
70 | buf.length = len(arg)
71 | if buf.length > 0:
72 | buf.data = (c_ubyte * buf.length).from_buffer_copy(arg)
73 | return buf
74 |
75 |
76 | def _load_library(lib_name: str) -> CDLL:
77 | lib_prefix_mapping = {"win32": ""}
78 | lib_suffix_mapping = {"darwin": ".dylib", "win32": ".dll"}
79 | try:
80 | os_name = sys.platform
81 | lib_prefix = lib_prefix_mapping.get(os_name, "lib")
82 | lib_suffix = lib_suffix_mapping.get(os_name, ".so")
83 | lib_path = os.path.join(
84 | os.path.dirname(__file__), f"{lib_prefix}{lib_name}{lib_suffix}"
85 | )
86 | return CDLL(lib_path)
87 | except KeyError:
88 | print ("Unknown platform for shared library")
89 | except OSError:
90 | print ("Library not loaded from python package")
91 |
92 | lib_path = find_library(lib_name)
93 | if not lib_path:
94 | if sys.platform == "darwin":
95 | ld = os.getenv("DYLD_LIBRARY_PATH")
96 | lib_path = os.path.join(ld, "liboberon.dylib")
97 | if os.path.exists(lib_path):
98 | return CDLL(lib_path)
99 |
100 | ld = os.getenv("DYLD_FALLBACK_LIBRARY_PATH")
101 | lib_path = os.path.join(ld, "liboberon.dylib")
102 | if os.path.exists(lib_path):
103 | return CDLL(lib_path)
104 | elif sys.platform != "win32":
105 | ld = os.getenv("LD_LIBRARY_PATH")
106 | lib_path = os.path.join(ld, "liboberon.so")
107 | if os.path.exists(lib_path):
108 | return CDLL(lib_path)
109 |
110 | raise Exception(f"Error loading library: {lib_name}")
111 | try:
112 | return CDLL(lib_path)
113 | except OSError as e:
114 | raise Exception(f"Error loading library: {lib_name}")
115 |
116 |
117 | def _get_library() -> CDLL:
118 | global LIB
119 | if LIB is None:
120 | LIB = _load_library("oberon")
121 |
122 | return LIB
123 |
124 |
125 | def _get_func(fn_name: str):
126 | return getattr(_get_library(), fn_name)
127 |
128 |
129 | def _get_size(fn_name: str) -> int:
130 | lib_fn = _get_func(fn_name)
131 | return lib_fn()
132 |
133 |
134 | def secret_key_size() -> int:
135 | return _get_size("oberon_secret_key_size")
136 |
137 |
138 | def public_key_size() -> int:
139 | return _get_size("oberon_public_key_size")
140 |
141 |
142 | def token_size() -> int:
143 | return _get_size("oberon_token_size")
144 |
145 |
146 | def blinding_size() -> int:
147 | return _get_size("oberon_blinding_size")
148 |
149 |
150 | def proof_size() -> int:
151 | return _get_size("oberon_proof_size")
152 |
153 |
154 | def _free_buffer(buffer: FfiByteBuffer):
155 | lib_fn = _get_func("oberon_byte_buffer_free")
156 | lib_fn(byref(buffer))
157 |
158 |
159 | def _free_string(err: FfiError):
160 | lib_fn = _get_func("oberon_string_free")
161 | lib_fn(byref(err))
162 |
163 |
164 | def _free_handle(handle: c_int64, err: FfiError):
165 | lib_fn = _get_func("oberon_create_proof_free")
166 | lib_fn(handle, byref(err))
167 |
168 |
169 | def new_secret_key() -> bytes:
170 | buffer = FfiByteBuffer()
171 | lib_fn = _get_func("oberon_new_secret_key")
172 | lib_fn(byref(buffer))
173 |
174 | result = _decode_bytes(buffer)
175 | #_free_buffer(buffer)
176 | return result
177 |
178 |
179 | def get_public_key(secret_key: bytes) -> bytes:
180 | """Return the corresponding public key from a secret key
181 | if the secret key is well-formed.
182 | """
183 | secret_key = _encode_bytes(secret_key)
184 | public_key = FfiByteBuffer()
185 | err = FfiError()
186 | lib_fn = _get_func("oberon_get_public_key")
187 | result = lib_fn(secret_key, byref(public_key), byref(err))
188 |
189 | if result == 0:
190 | out = _decode_bytes(public_key)
191 | #_free_buffer(public_key)
192 | return out
193 | else:
194 | message = string_at(err.message)
195 | #_free_string(err)
196 | raise Exception(message)
197 |
198 |
199 | def secret_key_from_seed(seed: bytes) -> bytes:
200 | """Create a secret key from a private seed."""
201 | i = _encode_bytes(seed)
202 | buffer = FfiByteBuffer()
203 | lib_fn = _get_func("oberon_secret_key_from_seed")
204 | lib_fn(i, byref(buffer))
205 |
206 | result = _decode_bytes(buffer)
207 | #_free_buffer(buffer)
208 | return result
209 |
210 |
211 | def new_token(secret_key: bytes, identifier: bytes) -> bytes:
212 | """Create a new token if the secret key is well-formed."""
213 | sk = _encode_bytes(secret_key)
214 | id = _encode_bytes(identifier)
215 | token = FfiByteBuffer()
216 | err = FfiError()
217 |
218 | lib_fn = _get_func("oberon_new_token")
219 | result = lib_fn(sk, id, byref(token), byref(err))
220 | if result == 0:
221 | out = _decode_bytes(token)
222 | #_free_buffer(token)
223 | return out
224 | else:
225 | message = string_at(err.message)
226 | #_free_string(err)
227 | raise Exception(message)
228 |
229 |
230 | def verify_token(token: bytes, public_key: bytes, identifier: bytes) -> bool:
231 | """Check if this token and identifier can be verified with this public key."""
232 | tk = _encode_bytes(token)
233 | pk = _encode_bytes(public_key)
234 | id = _encode_bytes(identifier)
235 | err = FfiError()
236 | lib_fn = _get_func("oberon_verify_token")
237 | result = lib_fn(tk, pk, id, byref(err))
238 | if result == 0:
239 | return True
240 | elif err.code != 0:
241 | message = string_at(err.message)
242 | #_free_string(err)
243 | raise Exception(message)
244 | else:
245 | return False
246 |
247 |
248 | def add_blinding(old_token: bytes, blinding: bytes) -> bytes:
249 | """Add a blinding factor to the token."""
250 | tk = _encode_bytes(old_token)
251 | b = _encode_bytes(blinding)
252 | err = FfiError()
253 | new = FfiByteBuffer()
254 | lib_fn = _get_func("oberon_add_blinding")
255 | result = lib_fn(tk, b, byref(new), byref(err))
256 |
257 | if result == 0:
258 | out = _decode_bytes(new)
259 | #_free_buffer(new)
260 | return out
261 | else:
262 | message = string_at(err.message)
263 | #_free_string(err)
264 | raise Exception(message)
265 |
266 |
267 | def remove_blinding(old_token: bytes, blinding: bytes) -> bytes:
268 | """Remove a blinding factor to the token."""
269 | tk = _encode_bytes(old_token)
270 | b = _encode_bytes(blinding)
271 | err = FfiError()
272 | new = FfiByteBuffer()
273 | lib_fn = _get_func("oberon_remove_blinding")
274 | result = lib_fn(tk, b, byref(new), byref(err))
275 |
276 | if result == 0:
277 | out = _decode_bytes(new)
278 | #_free_buffer(new)
279 | return out
280 | else:
281 | message = string_at(err.message)
282 | #_free_string(err)
283 | raise Exception(message)
284 |
285 |
286 | def create_proof(token: bytes, identifier: bytes, blindings: list[bytes], nonce: bytes) -> bytes:
287 | tk = _encode_bytes(token)
288 | id = _encode_bytes(identifier)
289 | n = _encode_bytes(nonce)
290 | err = FfiError()
291 | lib_fn = _get_func("oberon_create_proof_init")
292 | lib_fn.restype = c_uint64
293 |
294 | handle = lib_fn(byref(err))
295 | if handle == 0:
296 | message = string_at(err.message)
297 | #_free_string(err)
298 | raise Exception(message)
299 |
300 | handle = c_uint64(handle)
301 |
302 | lib_fn = _get_func("oberon_create_proof_set_token")
303 | result = lib_fn(handle, tk, byref(err))
304 | if result != 0:
305 | message = string_at(err.message)
306 | #_free_string(err)
307 | _free_handle(handle, err)
308 | raise Exception(message)
309 |
310 | lib_fn = _get_func("oberon_create_proof_set_id")
311 | result = lib_fn(handle, id, byref(err))
312 | if result != 0:
313 | message = string_at(err.message)
314 | #_free_string(err)
315 | _free_handle(handle, err)
316 | raise Exception(message)
317 |
318 | lib_fn = _get_func("oberon_create_proof_set_nonce")
319 | result = lib_fn(handle, n, byref(err))
320 | if result != 0:
321 | message = string_at(err.message)
322 | #_free_string(err)
323 | _free_handle(handle, err)
324 | raise Exception(message)
325 |
326 | lib_fn = _get_func("oberon_create_proof_add_blinding")
327 | for blinder in blindings:
328 | b = _encode_bytes(blinder)
329 | result = lib_fn(handle, b, byref(err))
330 | if result != 0:
331 | message = string_at(err.message)
332 | #_free_string(err)
333 | _free_handle(handle, err)
334 | raise Exception(message)
335 |
336 | lib_fn = _get_func("oberon_create_proof_finish")
337 | proof = FfiByteBuffer()
338 | result = lib_fn(handle, byref(proof), byref(err))
339 | if result == 0:
340 | _free_handle(handle, err)
341 | out = _decode_bytes(proof)
342 | #_free_buffer(new)
343 | return out
344 | else:
345 | message = string_at(err.message)
346 | _free_handle(handle, err)
347 | #_free_string(err)
348 | raise Exception(message)
349 |
350 |
351 | def verify_proof(proof: bytes, public_key: bytes, identifier: bytes, nonce: bytes) -> bool:
352 | pf = _encode_bytes(proof)
353 | pk = _encode_bytes(public_key)
354 | id = _encode_bytes(identifier)
355 | n = _encode_bytes(nonce)
356 | err = FfiError()
357 |
358 | lib_fn = _get_func("oberon_verify_proof")
359 | result = lib_fn(pf, pk, id, n, byref(err))
360 | if result == 0:
361 | return True
362 | elif err.code != 0:
363 | message = string_at(err.message)
364 | #_free_string(err)
365 | raise Exception(message)
366 | else:
367 | return False
368 |
369 |
370 | if __name__ == "__main__":
371 | pdb.set_trace()
372 | sk = new_secret_key()
373 | pk = get_public_key(sk)
374 | id = bytes("ed25519".encode("utf-8"))
375 | token = new_token(sk, id)
376 | print("verify: ", verify_token(token, pk, id))
377 | proof = create_proof(token, id, [], sk)
378 | print("open: ", verify_proof(proof, pk, id, sk))
379 |
--------------------------------------------------------------------------------
/python/oberon/classes.py:
--------------------------------------------------------------------------------
1 | from .bindings import secret_key_size, public_key_size, token_size, \
2 | proof_size, new_secret_key, \
3 | get_public_key, secret_key_from_seed, new_token, \
4 | verify_token, add_blinding, remove_blinding, \
5 | create_proof, verify_proof
6 |
7 | from typing import Optional, Union
8 |
9 |
10 | def from_seed(seed):
11 | return SecretKey(secret_key_from_seed(seed))
12 |
13 |
14 | class SecretKey:
15 | """A secret signing key"""
16 |
17 | def __init__(self, value: Optional[Union[str, bytes, list]] = None):
18 | data = bytes()
19 | if isinstance(value, memoryview):
20 | data = value
21 | if isinstance(value, bytearray):
22 | data = value
23 | if value is not None:
24 | if isinstance(value, str):
25 | data = value.encode("utf-8")
26 | if len(data) == secret_key_size():
27 | self.value = data
28 | else:
29 | self.value = new_secret_key()
30 |
31 | def public_key(self):
32 | return PublicKey(get_public_key(self.value))
33 |
34 | def new_token(self, identifier: bytes):
35 | return Token(new_token(self.value, identifier))
36 |
37 | def __bytes__(self):
38 | return self.value
39 |
40 |
41 | class PublicKey:
42 | """A public verification key"""
43 |
44 | def __init__(self, value: bytes):
45 | if len(value) == public_key_size():
46 | self.value = value
47 | else:
48 | raise Exception("invalid public key size")
49 |
50 | def __bytes__(self):
51 | return self.value
52 |
53 |
54 | class Token:
55 | """An Oberon Token"""
56 |
57 | def __init__(self, value: bytes):
58 | if len(value) == token_size():
59 | self.value = value
60 | else:
61 | raise Exception("invalid size")
62 |
63 | def __bytes__(self):
64 | return self.value
65 |
66 | def add_blinding(self, blinder: bytes):
67 | return Token(add_blinding(self.value, blinder))
68 |
69 | def remove_blinding(self, blinder: bytes):
70 | return Token(remove_blinding(self.value, blinder))
71 |
72 | def create_proof(self, identifier: bytes, blindings: list[bytes], nonce: bytes):
73 | return Proof(create_proof(self.value, identifier, blindings, nonce))
74 |
75 | def verify(self, identifier: bytes, public_key: PublicKey):
76 | return verify_token(self.value, public_key.value, identifier)
77 |
78 |
79 | class Proof:
80 | """An oberon proof"""
81 |
82 | def __init__(self, value: bytes):
83 | if len(value) == proof_size():
84 | self.value = value
85 | else:
86 | raise Exception("invalid proof size")
87 |
88 | def __bytes__(self):
89 | return self.value
90 |
91 | def verify(self, identifier: bytes, public_key: PublicKey, nonce: bytes):
92 | return verify_proof(self.value, public_key.value, identifier, nonce)
93 |
--------------------------------------------------------------------------------
/python/oberon/error.py:
--------------------------------------------------------------------------------
1 | """Error classes."""
2 |
3 | from enum import IntEnum
4 |
5 | class OberonErrorCode(IntEnum):
6 | SUCCESS = 0
7 | INPUT = 1
8 | SIGNING = 2
9 | WRAPPER = 99
10 |
11 | class OberonError(Exception):
12 | def __init__(self, code: OberonErrorCode, message: str, extra: str = None):
13 | super().__init__(message)
14 | self.code = code
15 | self.extra = extra
16 |
--------------------------------------------------------------------------------
/python/oberon/version.py:
--------------------------------------------------------------------------------
1 | """Oberon library wrapper version."""
2 |
3 | __version__ = "1.2.1"
4 |
--------------------------------------------------------------------------------
/python/readme.md:
--------------------------------------------------------------------------------
1 | # Oberon Python
2 |
3 | This is the python wrapper around the Oberon authentication scheme.
4 |
5 | To install use `pip install oberon` but this only installs the python code.
6 | Oberon is written as a C callable library and must be installed.
7 |
8 | `curl -sSLO https://github.com/cryptidtech/oberon/releases/`
9 |
10 | This library provides four classes: SecretKey, PublicKey, Token, Proof.
11 |
12 |
13 | SecretKey is used to create new tokens.
14 |
15 | PublicKey is used to verify tokens and token proofs.
16 |
17 | Tokens are used for proving ownership in zero-knowledge proofs.
18 | Tokens can be blinded provided the same blinders are supplied to create proofs.
19 |
20 | Proofs demonstrate a valid token with an identifier.
21 |
22 | ```python
23 | sk = SecretKey()
24 | pk = sk.public_key()
25 | id = bytes("ed25519".encode("utf-8"))
26 | token = sk.new_token(id)
27 | print("verify: ", token.verify(id, pk))
28 | proof = token.create_proof(id, [], b"a random nonce")
29 | print("open: ", proof.verify(id, pk, b"a random nonce"))
30 | ```
31 |
32 | Blindings can be applied as follows
33 |
34 | ```python
35 | blind_token = token.add_blinding(b"<4-6 digit pin>")
36 | ```
37 |
38 | Blindings can be removed as follows
39 |
40 | ```python
41 | token = blind_token.remove_blinding(b"<4-6 digit pin>")
42 | ```
--------------------------------------------------------------------------------
/python/setup.cfg:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | exclude = */test/**
4 | extend_ignore = D202, W503
5 | per_file_ignores = */__init__.py:D104
6 |
--------------------------------------------------------------------------------
/python/setup.py:
--------------------------------------------------------------------------------
1 | """Module setup."""
2 |
3 | import runpy
4 | from setuptools import find_packages, setup
5 |
6 | PACKAGE_NAME = "oberon"
7 | version_meta = runpy.run_path("./{}/version.py".format(PACKAGE_NAME))
8 | VERSION = version_meta["__version__"]
9 |
10 | if __name__ == "__main__":
11 | setup(
12 | name=PACKAGE_NAME,
13 | version=VERSION,
14 | author="Michael Lodder ",
15 | url="https://github.com/mikelodder7/oberon",
16 | packages=find_packages(),
17 | include_package_data=True,
18 | package_data={
19 | "": [
20 | "oberon.dll",
21 | "liboberon.dylib",
22 | "liboberon.so",
23 | ]
24 | },
25 | python_requires=">=3.7.3",
26 | classifiers=[
27 | "Programming Language :: Python :: 3",
28 | "License :: OSI Approved :: Apache Software License",
29 | ],
30 | )
31 |
--------------------------------------------------------------------------------
/rust/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "oberon"
3 | authors = ["Michael Lodder "]
4 | description = """
5 | Oberon is a multi-factor zero-knowledge capable token
6 | without requiring email, SMS, or authenticator apps.
7 | The proof of token validity is only 96 bytes while the token itself is only 48 bytes.
8 | """
9 | edition = "2021"
10 | keywords = ["zero-knowledge", "cryptography", "authentication"]
11 | categories = ["no-std", "wasm", "network-programming", "cryptography", "authentication"]
12 | homepage = "https://github.com/mikelodder7/oberon"
13 | license = "Apache-2.0"
14 | readme = "../README.md"
15 | repository = "https://github.com/mikelodder7/oberon"
16 | version = "2.2.1"
17 |
18 | [lib]
19 | crate-type = ["rlib", "cdylib"]
20 |
21 | [profile.release]
22 | debug = false
23 | lto = true
24 | opt-level = 3
25 |
26 | [features]
27 | default = ["rust"]
28 | alloc = ["bls12_381_plus/alloc", "digest/alloc"]
29 | ffi = ["ffi-support", "lazy_static", "rand/default", "std"]
30 | php = ["ext-php-rs", "rand/default", "std"]
31 | python = ["pyo3", "rand/default", "std"]
32 | rust = ["bls12_381_plus"]
33 | std = ["blstrs_plus", "digest/std", "sha3/std"]
34 | wasm = ["getrandom", "rand/default", "wasm-bindgen", "serde_json", "std"]
35 |
36 | [dependencies]
37 | bls12_381_plus = { version = "^0.8.4", optional = true }
38 | blstrs_plus = { version = "^0.8.4", features = ["portable"], optional = true }
39 | digest = { version = "0.10", default-features = false }
40 | ext-php-rs = { version = "0.10.0", optional = true }
41 | ffi-support = { version = "0.4", optional = true }
42 | getrandom = { version = "0.2", features = ["js"], optional = true }
43 | lazy_static = { version = "1.4", optional = true }
44 | pyo3 = { version = "0.19", features = ["extension-module"], optional = true }
45 | rand_core = "0.6"
46 | rand = { version = "0.8", default-features = false, optional = true }
47 | serde = { version = "1.0", features = ["derive"] }
48 | serde_json = { version = "1.0", optional = true }
49 | sha2 = { version = "0.10", default-features = false }
50 | sha3 = { version = "0.10", default-features = false }
51 | subtle = "2.4"
52 | wasm-bindgen = { version = "0.2", default-features = false, features = ["serde-serialize"], optional = true }
53 | zeroize = { version = "1", features = ["zeroize_derive"] }
54 |
55 | [dev-dependencies]
56 | criterion = "0.5"
57 | hex = "0.4"
58 | kmeans = "0.2"
59 | rand = "0.8"
60 | rand_chacha = "0.3"
61 | rand_xorshift = "0.3"
62 | random_tester = { version = "0.1", path = "../../random_tester" }
63 | serde_json = "1.0"
64 | serde_cbor = "0.11"
65 | serde_bare = "0.5"
66 |
67 | [[bench]]
68 | name = "random"
69 | harness = false
70 |
71 | [[example]]
72 | name = "random_test"
73 | path = "examples/random_test.rs"
74 |
--------------------------------------------------------------------------------
/rust/Cargo.toml.python:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "oberon"
3 | authors = ["Michael Lodder "]
4 | description = """
5 | Oberon is a multi-factor zero-knowledge capable token
6 | without requiring email, SMS, or authenticator apps.
7 | The proof of token validity is only 96 bytes while the token itself is only 48 bytes.
8 | """
9 | edition = "2021"
10 | keywords = ["zero-knowledge", "cryptography", "authentication"]
11 | categories = ["no-std", "wasm", "network-programming", "cryptography", "authentication"]
12 | homepage = "https://github.com/mikelodder7/oberon"
13 | license = "Apache-2.0"
14 | readme = "../README.md"
15 | repository = "https://github.com/mikelodder7/oberon"
16 | version = "2.0.0"
17 |
18 | [lib]
19 | crate-type = ["rlib", "cdylib"]
20 |
21 | [profile.release]
22 | debug = false
23 | lto = true
24 | opt-level = 3
25 |
26 | [features]
27 | default = []
28 | alloc = ["bls12_381_plus/alloc", "digest/alloc"]
29 | ffi = ["ffi-support", "lazy_static", "rand/default", "std"]
30 | php = ["ext-php-rs", "rand/default", "std"]
31 | python = ["pyo3", "rand/default", "std"]
32 | std = ["digest/std", "sha3/std"]
33 | wasm = ["getrandom", "rand/default", "wasm-bindgen"]
34 |
35 | [dependencies]
36 | bls12_381_plus = "=0.5.5"
37 | digest = { version = "0.9", default-features = false }
38 | ext-php-rs = { version = "0.8", optional = true }
39 | ff = "0.12"
40 | ffi-support = { version = "0.4", optional = true }
41 | getrandom = { version = "0.2", features = ["js"], optional = true }
42 | group = "0.12"
43 | lazy_static = { version = "1.4", optional = true }
44 | pyo3 = { version = "0.17", features = ["extension-module"], optional = true }
45 | rand_core = "0.6"
46 | rand = { version = "0.8", default-features = false, optional = true }
47 | serde = { version = "1.0", features = ["derive"] }
48 | sha3 = { version = "0.9", default-features = false }
49 | subtle = "2.4"
50 | wasm-bindgen = { version = "0.2", default-features = false, features = ["serde-serialize"], optional = true }
51 | zeroize = { version = "1.5", features = ["zeroize_derive"] }
52 |
53 | [dev-dependencies]
54 | criterion = "0.4"
55 | hex = "0.4"
56 | kmeans = "0.2"
57 | rand = "0.8"
58 | rand_chacha = "0.3"
59 | rand_xorshift = "0.3"
60 | random_tester = { version = "0.1", path = "../../random_tester" }
61 | serde_json = "1.0"
62 | serde_cbor = "0.11"
63 | serde_bare = "0.5"
64 |
65 | [[bench]]
66 | name = "random"
67 | harness = false
68 |
69 | [[example]]
70 | name = "random_test"
71 | path = "examples/random_test.rs"
--------------------------------------------------------------------------------
/rust/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:22.04
2 |
3 | ENV DEBIAN_FRONTEND=noninteractive
4 | ENV PATH=/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
5 | RUN apt update && apt upgrade -y
6 | RUN apt install -y curl build-essential cmake libssl-dev pkg-config
7 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
8 |
--------------------------------------------------------------------------------
/rust/Dockerfile.php:
--------------------------------------------------------------------------------
1 | FROM ubuntu:20.04
2 |
3 | ENV DEBIAN_FRONTEND=noninteractive
4 | ENV PATH=/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
5 |
6 | RUN apt update && apt upgrade -y
7 | RUN apt install -y curl build-essential software-properties-common clang
8 | RUN add-apt-repository ppa:ondrej/php
9 | RUN apt update
10 | RUN apt install -y php8.1 php8.1-dev
11 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
12 | RUN cargo install cargo-php
13 |
--------------------------------------------------------------------------------
/rust/Dockerfile.python:
--------------------------------------------------------------------------------
1 | FROM ubuntu:20.04
2 |
3 | ENV DEBIAN_FRONTEND=noninteractive
4 | ENV PATH=/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
5 |
6 | RUN apt update && apt upgrade -y
7 | RUN apt install -y curl build-essential python3 python3-pip
8 | RUN pip3 install maturin
9 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
10 |
--------------------------------------------------------------------------------
/rust/LICENSE:
--------------------------------------------------------------------------------
1 | ../LICENSE
--------------------------------------------------------------------------------
/rust/benches/random.rs:
--------------------------------------------------------------------------------
1 | use criterion::*;
2 | use oberon::*;
3 | use rand::rngs::OsRng;
4 | use rand::thread_rng;
5 | use rand_chacha::ChaChaRng;
6 | use rand_core::{CryptoRng, RngCore, SeedableRng};
7 | use rand_xorshift::XorShiftRng;
8 |
9 | struct XorShiftRngWrapper(XorShiftRng);
10 |
11 | impl SeedableRng for XorShiftRngWrapper {
12 | type Seed = [u8; 16];
13 |
14 | fn from_seed(seed: Self::Seed) -> Self {
15 | Self(XorShiftRng::from_seed(seed))
16 | }
17 | }
18 |
19 | impl RngCore for XorShiftRngWrapper {
20 | fn next_u32(&mut self) -> u32 {
21 | self.0.next_u32()
22 | }
23 |
24 | fn next_u64(&mut self) -> u64 {
25 | self.0.next_u64()
26 | }
27 |
28 | fn fill_bytes(&mut self, buffer: &mut [u8]) {
29 | self.0.fill_bytes(buffer)
30 | }
31 |
32 | fn try_fill_bytes(&mut self, buffer: &mut [u8]) -> Result<(), rand_core::Error> {
33 | self.0.try_fill_bytes(buffer)
34 | }
35 | }
36 |
37 | impl CryptoRng for XorShiftRngWrapper {}
38 |
39 | fn setup(rng: &mut R) -> ([u8; 16], [u8; 16], Token, SecretKey) {
40 | let mut sk_seed = [0u8; 16];
41 | let mut id = [0u8; 16];
42 | let mut nonce = [0u8; 16];
43 | rng.fill_bytes(&mut sk_seed);
44 | rng.fill_bytes(&mut id);
45 | rng.fill_bytes(&mut nonce);
46 |
47 | let sk = SecretKey::hash(&sk_seed);
48 | (id, nonce, sk.sign(&id).unwrap(), sk)
49 | }
50 |
51 | fn signing(c: &mut Criterion) {
52 | let mut id = [0u8; 16];
53 | let mut sk_seed = [0u8; 16];
54 | thread_rng().fill_bytes(&mut sk_seed);
55 | thread_rng().fill_bytes(&mut id);
56 | let sk = SecretKey::hash(&sk_seed);
57 | c.bench_function("token generation", |b| b.iter(|| sk.sign(&id).unwrap()));
58 | }
59 |
60 | fn token_verify(c: &mut Criterion) {
61 | let mut id = [0u8; 16];
62 | let mut sk_seed = [0u8; 16];
63 | thread_rng().fill_bytes(&mut sk_seed);
64 | thread_rng().fill_bytes(&mut id);
65 | let sk = SecretKey::hash(&sk_seed);
66 | let pk = PublicKey::from(&sk);
67 | let token = sk.sign(&id).unwrap();
68 | c.bench_function("token verification", |b| b.iter(|| token.verify(pk, id)));
69 | }
70 |
71 | fn xof_shift_rng(c: &mut Criterion) {
72 | let mut rng = XorShiftRngWrapper::from_seed([7u8; 16]);
73 | let (id, nonce, token, _) = setup(&mut rng);
74 | c.bench_function("xor shift rng proof generation", |b| {
75 | b.iter(|| {
76 | let _ = Proof::new(&token, &[], id, &nonce, &mut rng);
77 | })
78 | });
79 | }
80 |
81 | fn os_rng(c: &mut Criterion) {
82 | let mut rng = OsRng;
83 | let (id, nonce, token, _) = setup(&mut rng);
84 | c.bench_function("os rng proof generation", |b| {
85 | b.iter(|| {
86 | let _ = Proof::new(&token, &[], id, &nonce, &mut rng);
87 | })
88 | });
89 | }
90 |
91 | fn chacha_rng(c: &mut Criterion) {
92 | let mut rng = ChaChaRng::from_seed([5u8; 32]);
93 | let (id, nonce, token, _) = setup(&mut rng);
94 | c.bench_function("chacha rng proof generation", |b| {
95 | b.iter(|| {
96 | let _ = Proof::new(&token, &[], id, &nonce, &mut rng);
97 | })
98 | });
99 | }
100 |
101 | fn proof_verify(c: &mut Criterion) {
102 | let (id, nonce, token, sk) = setup(&mut thread_rng());
103 | let proof = Proof::new(&token, &[], id, &nonce, thread_rng()).unwrap();
104 | let pk = PublicKey::from(&sk);
105 | c.bench_function("proof verification", |b| {
106 | b.iter(|| proof.open(pk, id, nonce))
107 | });
108 | }
109 |
110 | fn blinding_factor(c: &mut Criterion) {
111 | c.bench_function("Blinding factor length 1", |b| {
112 | b.iter(|| Blinding::new(&[1u8]))
113 | });
114 | c.bench_function("Blinding factor length 2", |b| {
115 | b.iter(|| Blinding::new(&[2u8; 2]))
116 | });
117 | c.bench_function("Blinding factor length 4", |b| {
118 | b.iter(|| Blinding::new(&[4u8; 4]))
119 | });
120 | c.bench_function("Blinding factor length 8", |b| {
121 | b.iter(|| Blinding::new(&[8u8; 8]))
122 | });
123 | c.bench_function("Blinding factor length 16", |b| {
124 | b.iter(|| Blinding::new(&[16u8; 16]))
125 | });
126 | }
127 |
128 | criterion_group!(
129 | benches,
130 | signing,
131 | token_verify,
132 | proof_verify,
133 | xof_shift_rng,
134 | chacha_rng,
135 | os_rng,
136 | blinding_factor
137 | );
138 | criterion_main!(benches);
139 |
--------------------------------------------------------------------------------
/rust/examples/random_test.rs:
--------------------------------------------------------------------------------
1 | use kmeans::*;
2 | use oberon::*;
3 | use rand::rngs::OsRng;
4 | use rand_chacha::ChaChaRng;
5 | use rand_core::{CryptoRng, RngCore, SeedableRng};
6 | use rand_xorshift::XorShiftRng;
7 | use random_tester::*;
8 |
9 | struct XorShiftRngWrapper(XorShiftRng);
10 |
11 | impl SeedableRng for XorShiftRngWrapper {
12 | type Seed = [u8; 16];
13 |
14 | fn from_seed(seed: Self::Seed) -> Self {
15 | Self(XorShiftRng::from_seed(seed))
16 | }
17 | }
18 |
19 | impl RngCore for XorShiftRngWrapper {
20 | fn next_u32(&mut self) -> u32 {
21 | self.0.next_u32()
22 | }
23 |
24 | fn next_u64(&mut self) -> u64 {
25 | self.0.next_u64()
26 | }
27 |
28 | fn fill_bytes(&mut self, buffer: &mut [u8]) {
29 | self.0.fill_bytes(buffer)
30 | }
31 |
32 | fn try_fill_bytes(&mut self, buffer: &mut [u8]) -> Result<(), rand_core::Error> {
33 | self.0.try_fill_bytes(buffer)
34 | }
35 | }
36 |
37 | impl CryptoRng for XorShiftRngWrapper {}
38 |
39 | fn main() {
40 | const SAMPLE_COUNT: usize = 4000;
41 | const SAMPLE_DIMENSIONS: usize = 256;
42 | const K: usize = 2;
43 | const MAX_ITER: usize = 1000;
44 |
45 | let mut rng = OsRng;
46 | let mut sk_seed = [0u8; 16];
47 | let mut id = [0u8; 16];
48 | let mut nonce = [0u8; 16];
49 | rng.fill_bytes(&mut sk_seed);
50 | rng.fill_bytes(&mut id);
51 | rng.fill_bytes(&mut nonce);
52 |
53 | let sk = SecretKey::hash(&sk_seed);
54 | let token = sk.sign(id).unwrap();
55 |
56 | let mut entropy_testers: Vec<(&'static str, &'static str, Box)> = vec![
57 | ("Shannon", ">= 7.9", Box::new(ShannonCalculation::default())),
58 | ("Mean", "= 127.0", Box::new(MeanCalculation::default())),
59 | (
60 | "MonteCarlo",
61 | "3.1 <=> 3.2",
62 | Box::new(MonteCarloCalculation::default()),
63 | ),
64 | (
65 | "Serial",
66 | "-0.004 <=> 0.004",
67 | Box::new(SerialCorrelationCoefficientCalculation::default()),
68 | ),
69 | (
70 | "ChiSquare",
71 | "0.001 <=> 0.99",
72 | Box::new(ChiSquareCalculation::default()),
73 | ),
74 | ];
75 |
76 | // Create proof samples using the same nonce
77 | // Not done in practice but used to isolate the effects
78 | // of the RNG
79 | let mut rngs = get_rngs();
80 | for (label, rng) in rngs.iter_mut() {
81 | println!("Sampling with {}", *label);
82 | let mut samples = vec![0f32; SAMPLE_DIMENSIONS * SAMPLE_COUNT];
83 | for i in 0..SAMPLE_COUNT {
84 | let proof = Proof::new(&token, &[], &id, &nonce, &mut *rng).unwrap();
85 | let proof_bytes = proof
86 | .to_bytes()
87 | .iter()
88 | .map(|b| *b as f32)
89 | .collect::>();
90 | for tester in entropy_testers.iter_mut() {
91 | tester.2.update(&proof.to_bytes());
92 | }
93 | samples[i * SAMPLE_DIMENSIONS..(i + 1) * SAMPLE_DIMENSIONS]
94 | .copy_from_slice(&proof_bytes);
95 | }
96 | println!("Computing clustering");
97 | let kmean = KMeans::new(samples, SAMPLE_COUNT, SAMPLE_DIMENSIONS);
98 | let result = kmean.kmeans_lloyd(
99 | K,
100 | MAX_ITER,
101 | KMeans::init_kmeanplusplus,
102 | &KMeansConfig::default(),
103 | );
104 |
105 | // println!("Centroids: {:?}", result.centroids);
106 | // println!("Cluster-assignments: {:?}", result.assignments);
107 | println!("Error: {}", result.distsum);
108 | for tester in entropy_testers.iter_mut() {
109 | println!("{}: {} - {}", tester.0, tester.1, tester.2.finalize());
110 | }
111 | }
112 | }
113 |
114 | enum Rngs {
115 | Os(OsRng),
116 | Xor(XorShiftRngWrapper),
117 | Cha(ChaChaRng),
118 | }
119 |
120 | impl RngCore for Rngs {
121 | fn next_u32(&mut self) -> u32 {
122 | match self {
123 | Self::Xor(x) => x.next_u32(),
124 | Self::Cha(c) => c.next_u32(),
125 | Self::Os(o) => o.next_u32(),
126 | }
127 | }
128 |
129 | fn next_u64(&mut self) -> u64 {
130 | match self {
131 | Self::Xor(x) => x.next_u64(),
132 | Self::Cha(c) => c.next_u64(),
133 | Self::Os(o) => o.next_u64(),
134 | }
135 | }
136 |
137 | fn fill_bytes(&mut self, buffer: &mut [u8]) {
138 | match self {
139 | Self::Xor(x) => x.fill_bytes(buffer),
140 | Self::Cha(c) => c.fill_bytes(buffer),
141 | Self::Os(o) => o.fill_bytes(buffer),
142 | }
143 | }
144 |
145 | fn try_fill_bytes(&mut self, buffer: &mut [u8]) -> Result<(), rand_core::Error> {
146 | match self {
147 | Self::Xor(x) => x.try_fill_bytes(buffer),
148 | Self::Cha(c) => c.try_fill_bytes(buffer),
149 | Self::Os(o) => o.try_fill_bytes(buffer),
150 | }
151 | }
152 | }
153 |
154 | impl CryptoRng for Rngs {}
155 |
156 | fn get_rngs() -> [(&'static str, Rngs); 3] {
157 | let mut rng = OsRng;
158 | let xs_rng = XorShiftRngWrapper::from_rng(&mut rng).unwrap();
159 | let cc_rng = ChaChaRng::from_rng(&mut rng).unwrap();
160 | let os_rng = OsRng;
161 | [
162 | ("XorShiftRng", Rngs::Xor(xs_rng)),
163 | ("ChaChaRng", Rngs::Cha(cc_rng)),
164 | ("OsRng", Rngs::Os(os_rng)),
165 | ]
166 | }
167 |
--------------------------------------------------------------------------------
/rust/publish_python.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -xv
4 | mkdir -p build/python/src
5 | cp pyproject.toml build/python/
6 | cp Cargo.toml.python build/python/Cargo.toml
7 | cp src/*.rs build/python/src/
8 | cp LICENSE build/python
9 | cp ../README.md build/
10 |
11 | maturin publish --cargo-extra-args="--features=python" --no-sdist --manifest-path=build/python/Cargo.toml
--------------------------------------------------------------------------------
/rust/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["maturin>=0.12,<0.13"]
3 | build-backend = "maturin"
4 |
5 | [project]
6 | name = "oberon"
7 | authors = [{ name="Michael Lodder" }]
8 | readme = "../README.md"
9 | license = { file = "LICENSE" }
10 | requires-python = ">=3.7"
11 | classifiers = [
12 | "Programming Language :: Rust",
13 | "Programming Language :: Python :: Implementation :: CPython",
14 | "Programming Language :: Python :: Implementation :: PyPy",
15 | ]
16 |
17 |
--------------------------------------------------------------------------------
/rust/src/blinding.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | use crate::inner_types::*;
6 | use crate::util::*;
7 | #[cfg(feature = "wasm")]
8 | use core::convert::TryFrom;
9 | use serde::{Deserialize, Serialize};
10 | use subtle::CtOption;
11 |
12 | /// A blinding factor is applied to a token to enable
13 | /// multi factor authentication
14 | ///
15 | /// ```
16 | /// use oberon::Blinding;
17 | ///
18 | /// let blinding = Blinding::new(b"1234");
19 | ///
20 | /// assert_ne!(blinding.to_bytes(), [0u8; Blinding::BYTES]);
21 | /// ```
22 | #[derive(Copy, Clone, Debug, Deserialize, Serialize)]
23 | pub struct Blinding(pub(crate) G1Projective);
24 |
25 | impl Default for Blinding {
26 | fn default() -> Self {
27 | Self(G1Projective::IDENTITY)
28 | }
29 | }
30 |
31 | #[cfg(feature = "wasm")]
32 | wasm_slice_impl!(Blinding);
33 |
34 | impl Blinding {
35 | /// The number of bytes in a blinding factor
36 | pub const BYTES: usize = 48;
37 |
38 | /// Create a new blinding factor
39 | pub fn new(data: &[u8]) -> Self {
40 | Self(hash_to_curve(data))
41 | }
42 |
43 | /// Convert this blinding factor into a byte sequence
44 | pub fn to_bytes(&self) -> [u8; Self::BYTES] {
45 | self.0.to_affine().to_compressed()
46 | }
47 |
48 | /// Convert a byte sequence to a blinding factor
49 | pub fn from_bytes(data: &[u8; 48]) -> CtOption {
50 | G1Affine::from_compressed(data).map(|p| Self(G1Projective::from(p)))
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/rust/src/ffi.rs:
--------------------------------------------------------------------------------
1 | #![allow(unused_doc_comments, missing_docs)]
2 | use crate::{Blinding, Proof, PublicKey, SecretKey, Token};
3 | use ffi_support::{
4 | define_bytebuffer_destructor, define_handle_map_deleter, define_string_destructor, ByteBuffer,
5 | ConcurrentHandleMap, ErrorCode, ExternError,
6 | };
7 | use lazy_static::lazy_static;
8 | use std::{ptr, slice, string::String, vec::Vec};
9 |
10 | lazy_static! {
11 | /// The context manager for creating proofs
12 | pub static ref CREATE_PROOF_CONTEXT: ConcurrentHandleMap =
13 | ConcurrentHandleMap::new();
14 | }
15 |
16 | /// Cleanup created strings
17 | define_string_destructor!(oberon_string_free);
18 | /// Cleanup created byte buffers
19 | define_bytebuffer_destructor!(oberon_byte_buffer_free);
20 | /// Cleanup created proof contexts
21 | define_handle_map_deleter!(CREATE_PROOF_CONTEXT, oberon_create_proof_free);
22 |
23 | /// The proof context object
24 | pub struct CreateProofContext {
25 | /// The proof token
26 | pub token: Option,
27 | /// The token blindings
28 | pub blindings: Vec,
29 | /// The id associated with the token
30 | pub id: Option>,
31 | /// The proof nonce
32 | pub nonce: Option>,
33 | }
34 |
35 | /// Used for receiving byte arrays
36 | #[repr(C)]
37 | pub struct ByteArray {
38 | length: usize,
39 | data: *const u8,
40 | }
41 |
42 | impl Default for ByteArray {
43 | fn default() -> Self {
44 | Self {
45 | length: 0,
46 | data: ptr::null(),
47 | }
48 | }
49 | }
50 |
51 | impl From<&Vec> for ByteArray {
52 | fn from(b: &Vec) -> Self {
53 | Self::from_slice(b.as_slice())
54 | }
55 | }
56 |
57 | impl From> for ByteArray {
58 | fn from(b: Vec) -> Self {
59 | Self::from_slice(b.as_slice())
60 | }
61 | }
62 |
63 | impl From for ByteArray {
64 | fn from(b: ByteBuffer) -> Self {
65 | Self::from_slice(&b.destroy_into_vec())
66 | }
67 | }
68 |
69 | impl ByteArray {
70 | /// Convert to a byte vector
71 | pub fn to_vec(&self) -> Vec {
72 | if self.data.is_null() || self.length == 0 {
73 | Vec::new()
74 | } else {
75 | unsafe { slice::from_raw_parts(self.data, self.length).to_vec() }
76 | }
77 | }
78 |
79 | /// Convert to a byte vector if possible
80 | /// Some if success
81 | /// None if failure
82 | pub fn to_opt_vec(&self) -> Option> {
83 | if self.data.is_null() {
84 | None
85 | } else if self.length == 0 {
86 | Some(Vec::new())
87 | } else {
88 | Some(unsafe { slice::from_raw_parts(self.data, self.length).to_vec() })
89 | }
90 | }
91 |
92 | /// Convert to outgoing ByteBuffer
93 | pub fn into_byte_buffer(self) -> ByteBuffer {
94 | ByteBuffer::from_vec(self.to_vec())
95 | }
96 |
97 | /// Convert from a slice
98 | pub fn from_slice>(data: I) -> Self {
99 | let data = data.as_ref();
100 | Self {
101 | length: data.len(),
102 | data: data.as_ptr(),
103 | }
104 | }
105 | }
106 |
107 | macro_rules! from_bytes {
108 | ($name:ident, $type:ident) => {
109 | fn $name(input: Vec) -> Option<$type> {
110 | match <[u8; $type::BYTES]>::try_from(input.as_slice()) {
111 | Err(_) => None,
112 | Ok(bytes) => {
113 | let val = $type::from_bytes(&bytes);
114 | if val.is_some().unwrap_u8() == 1u8 {
115 | Some(val.unwrap())
116 | } else {
117 | None
118 | }
119 | }
120 | }
121 | }
122 | };
123 | }
124 | from_bytes!(secret_key, SecretKey);
125 | from_bytes!(public_key, PublicKey);
126 | from_bytes!(get_token, Token);
127 | from_bytes!(get_proof, Proof);
128 |
129 | /// The size of the secret key
130 | #[no_mangle]
131 | pub extern "C" fn oberon_secret_key_size() -> i32 {
132 | SecretKey::BYTES as i32
133 | }
134 |
135 | /// The size of the public key
136 | #[no_mangle]
137 | pub extern "C" fn oberon_public_key_size() -> i32 {
138 | PublicKey::BYTES as i32
139 | }
140 |
141 | /// The size of the token
142 | #[no_mangle]
143 | pub extern "C" fn oberon_token_size() -> i32 {
144 | Token::BYTES as i32
145 | }
146 |
147 | /// The size of a blinding
148 | #[no_mangle]
149 | pub extern "C" fn oberon_blinding_size() -> i32 {
150 | Blinding::BYTES as i32
151 | }
152 |
153 | /// The size of a proof
154 | #[no_mangle]
155 | pub extern "C" fn oberon_proof_size() -> i32 {
156 | Proof::BYTES as i32
157 | }
158 |
159 | /// Create new random secret key
160 | #[no_mangle]
161 | pub extern "C" fn oberon_new_secret_key(secret_key: &mut ByteBuffer) -> i32 {
162 | let sk = SecretKey::new(rand::thread_rng());
163 | *secret_key = ByteBuffer::from_vec(sk.to_bytes().to_vec());
164 | 0
165 | }
166 |
167 | /// Get the public key from the secret key
168 | #[no_mangle]
169 | pub extern "C" fn oberon_get_public_key(
170 | sk: ByteArray,
171 | public_key: &mut ByteBuffer,
172 | err: &mut ExternError,
173 | ) -> i32 {
174 | let t = sk.to_vec();
175 | match secret_key(t) {
176 | None => {
177 | *err = ExternError::new_error(ErrorCode::new(1), String::from("Invalid secret key"));
178 | 1
179 | }
180 | Some(sk) => {
181 | let pk = PublicKey::from(&sk);
182 | *public_key = ByteBuffer::from_vec(pk.to_bytes().to_vec());
183 | 0
184 | }
185 | }
186 | }
187 |
188 | /// Create new secret key from a seed
189 | #[no_mangle]
190 | pub extern "C" fn oberon_secret_key_from_seed(seed: ByteArray, sk: &mut ByteBuffer) -> i32 {
191 | let t = SecretKey::hash(seed.to_vec().as_slice());
192 | *sk = ByteBuffer::from_vec(t.to_bytes().to_vec());
193 | 0
194 | }
195 |
196 | /// Create a new token for a given ID
197 | #[no_mangle]
198 | pub extern "C" fn oberon_new_token(
199 | sk: ByteArray,
200 | id: ByteArray,
201 | token: &mut ByteBuffer,
202 | err: &mut ExternError,
203 | ) -> i32 {
204 | let t = sk.to_vec();
205 | match secret_key(t) {
206 | None => {
207 | *err = ExternError::new_error(ErrorCode::new(1), String::from("Invalid secret key"));
208 | 1
209 | }
210 | Some(sk) => match Token::new(&sk, id.to_vec()) {
211 | None => {
212 | *err = ExternError::new_error(
213 | ErrorCode::new(2),
214 | String::from("Unable to create token"),
215 | );
216 | 2
217 | }
218 | Some(tk) => {
219 | *token = ByteBuffer::from_vec(tk.to_bytes().to_vec());
220 | 0
221 | }
222 | },
223 | }
224 | }
225 |
226 | /// Verify a token for a given ID
227 | #[no_mangle]
228 | pub extern "C" fn oberon_verify_token(
229 | token: ByteArray,
230 | pk: ByteArray,
231 | id: ByteArray,
232 | err: &mut ExternError,
233 | ) -> i32 {
234 | match (get_token(token.to_vec()), public_key(pk.to_vec())) {
235 | (Some(tk), Some(pk)) => {
236 | let res = tk.verify(pk, id.to_vec()).unwrap_u8() as i32;
237 | -(res - 1)
238 | }
239 | (_, _) => {
240 | *err = ExternError::new_error(
241 | ErrorCode::new(1),
242 | String::from("Invalid token and/or public key"),
243 | );
244 | 1
245 | }
246 | }
247 | }
248 |
249 | /// Create a blinding factor from the specified data
250 | #[no_mangle]
251 | pub extern "C" fn oberon_create_blinding(data: ByteArray, blinding: &mut ByteBuffer) -> i32 {
252 | *blinding = ByteBuffer::from_vec(Blinding::new(data.to_vec().as_slice()).to_bytes().to_vec());
253 | 0
254 | }
255 |
256 | /// Adds a blinding factor to the token
257 | #[no_mangle]
258 | pub extern "C" fn oberon_add_blinding(
259 | old_token: ByteArray,
260 | data: ByteArray,
261 | new_token: &mut ByteBuffer,
262 | err: &mut ExternError,
263 | ) -> i32 {
264 | match get_token(old_token.to_vec()) {
265 | None => {
266 | *err = ExternError::new_error(ErrorCode::new(1), String::from("Invalid token"));
267 | 1
268 | }
269 | Some(tk) => {
270 | let b = Blinding::new(data.to_vec().as_slice());
271 | let new_tk = tk - b;
272 | *new_token = ByteBuffer::from_vec(new_tk.to_bytes().to_vec());
273 | 0
274 | }
275 | }
276 | }
277 |
278 | /// Removes a blinding factor to the token
279 | #[no_mangle]
280 | pub extern "C" fn oberon_remove_blinding(
281 | old_token: ByteArray,
282 | data: ByteArray,
283 | new_token: &mut ByteBuffer,
284 | err: &mut ExternError,
285 | ) -> i32 {
286 | match get_token(old_token.to_vec()) {
287 | None => {
288 | *err = ExternError::new_error(ErrorCode::new(1), String::from("Invalid token"));
289 | 1
290 | }
291 | Some(tk) => {
292 | let b = Blinding::new(data.to_vec().as_slice());
293 | let new_tk = tk + b;
294 | *new_token = ByteBuffer::from_vec(new_tk.to_bytes().to_vec());
295 | 0
296 | }
297 | }
298 | }
299 |
300 | /// Creates a proof context
301 | #[no_mangle]
302 | pub extern "C" fn oberon_create_proof_init(err: &mut ExternError) -> u64 {
303 | CREATE_PROOF_CONTEXT.insert_with_output(err, || CreateProofContext {
304 | token: None,
305 | id: None,
306 | blindings: Vec::new(),
307 | nonce: None,
308 | })
309 | }
310 |
311 | /// Set the proof token
312 | #[no_mangle]
313 | pub extern "C" fn oberon_create_proof_set_token(
314 | handle: u64,
315 | token: ByteArray,
316 | err: &mut ExternError,
317 | ) -> i32 {
318 | CREATE_PROOF_CONTEXT.call_with_result_mut(err, handle, move |ctx| -> Result<(), ExternError> {
319 | match get_token(token.to_vec()) {
320 | None => Err(ExternError::new_error(
321 | ErrorCode::new(1),
322 | String::from("Invalid token"),
323 | )),
324 | Some(tk) => {
325 | ctx.token = Some(tk);
326 | Ok(())
327 | }
328 | }
329 | });
330 | err.get_code().code()
331 | }
332 |
333 | /// Set the proof id
334 | #[no_mangle]
335 | pub extern "C" fn oberon_create_proof_set_id(
336 | handle: u64,
337 | id: ByteArray,
338 | err: &mut ExternError,
339 | ) -> i32 {
340 | CREATE_PROOF_CONTEXT.call_with_output_mut(err, handle, move |ctx| {
341 | ctx.id = Some(id.to_vec());
342 | });
343 | err.get_code().code()
344 | }
345 |
346 | /// Set the proof nonce
347 | #[no_mangle]
348 | pub extern "C" fn oberon_create_proof_set_nonce(
349 | handle: u64,
350 | nonce: ByteArray,
351 | err: &mut ExternError,
352 | ) -> i32 {
353 | CREATE_PROOF_CONTEXT.call_with_output_mut(err, handle, move |ctx| {
354 | ctx.nonce = Some(nonce.to_vec());
355 | });
356 | err.get_code().code()
357 | }
358 |
359 | /// Set the proof blinding
360 | #[no_mangle]
361 | pub extern "C" fn oberon_create_proof_add_blinding(
362 | handle: u64,
363 | blinding: ByteArray,
364 | err: &mut ExternError,
365 | ) -> i32 {
366 | CREATE_PROOF_CONTEXT.call_with_output_mut(err, handle, move |ctx| {
367 | ctx.blindings
368 | .push(Blinding::new(blinding.to_vec().as_slice()));
369 | });
370 | err.get_code().code()
371 | }
372 |
373 | /// Create the proof
374 | #[no_mangle]
375 | pub extern "C" fn oberon_create_proof_finish(
376 | handle: u64,
377 | proof: &mut ByteBuffer,
378 | err: &mut ExternError,
379 | ) -> i32 {
380 | let pf = CREATE_PROOF_CONTEXT.call_with_result(
381 | err,
382 | handle,
383 | move |ctx| -> Result {
384 | if ctx.id.is_none() {
385 | return Err(ExternError::new_error(
386 | ErrorCode::new(1),
387 | String::from("Id must be set"),
388 | ));
389 | }
390 | if ctx.nonce.is_none() {
391 | return Err(ExternError::new_error(
392 | ErrorCode::new(2),
393 | String::from("Nonce must be set"),
394 | ));
395 | }
396 | if ctx.token.is_none() {
397 | return Err(ExternError::new_error(
398 | ErrorCode::new(3),
399 | String::from("Token must be set"),
400 | ));
401 | }
402 | match (ctx.id.as_ref(), ctx.nonce.as_ref(), ctx.token.as_ref()) {
403 | (Some(id), Some(nonce), Some(token)) => {
404 | match Proof::new(
405 | token,
406 | ctx.blindings.as_slice(),
407 | id,
408 | nonce,
409 | rand::thread_rng(),
410 | ) {
411 | None => Err(ExternError::new_error(
412 | ErrorCode::new(4),
413 | String::from("Invalid proof parameters"),
414 | )),
415 | Some(p) => Ok(ByteBuffer::from_vec(p.to_bytes().to_vec())),
416 | }
417 | }
418 | (_, _, _) => Err(ExternError::new_error(
419 | ErrorCode::new(5),
420 | String::from("Invalid parameters"),
421 | )),
422 | }
423 | },
424 | );
425 | if err.get_code().is_success() {
426 | *proof = pf;
427 | if let Err(e) = CREATE_PROOF_CONTEXT.remove_u64(handle) {
428 | *err = ExternError::new_error(ErrorCode::new(6), std::format!("{:?}", e))
429 | }
430 | }
431 | err.get_code().code()
432 | }
433 |
434 | /// Creates a proof using a nonce received from a verifier
435 | #[no_mangle]
436 | pub extern "C" fn oberon_verify_proof(
437 | proof: ByteArray,
438 | pk: ByteArray,
439 | id: ByteArray,
440 | nonce: ByteArray,
441 | err: &mut ExternError,
442 | ) -> i32 {
443 | match (get_proof(proof.to_vec()), public_key(pk.to_vec())) {
444 | (Some(pf), Some(pub_key)) => {
445 | let res = pf
446 | .open(pub_key, id.to_vec().as_slice(), nonce.to_vec().as_slice())
447 | .unwrap_u8() as i32;
448 | -(res - 1)
449 | }
450 | (_, _) => {
451 | *err = ExternError::new_error(
452 | ErrorCode::new(1),
453 | String::from("Invalid proof and/or public key"),
454 | );
455 | 1
456 | }
457 | }
458 | }
459 |
--------------------------------------------------------------------------------
/rust/src/lib.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | //! Oberon allows endpoints to issue multi-factor capable tokens to consumers
6 | //! who can prove their validity with disclosing the tokens themselves without requiring
7 | //! email, SMS, or authenticator apps. Endpoints
8 | //! only need to store a single public key and not any tokens. An attacker that breaks
9 | //! into the server doesn't have any password/token files to steal and only would see
10 | //! a public key. The proof of token validity is only 96 bytes while the token itself
11 | //! is only 48 bytes. The issuing party and verifying servers can be separate entities.
12 | //!
13 | //! Tokens are created by signing an identitifier with a secret key.
14 | //!
15 | //! Tokens are presented to a verifier as a zero-knowledge proof such that
16 | //! the verifier never learns the value of the token. Additional blindings
17 | //! can be applied to the token so additional security factors are required
18 | //! before using it. One example is a pin or password.
19 | //!
20 | //! ```
21 | //! use oberon::*;
22 | //! use rand::thread_rng;
23 | //!
24 | //! let sk = SecretKey::hash(b"my super secret key seed");
25 | //! let pk = PublicKey::from(&sk);
26 | //! let id = b"test identity";
27 | //! let token = sk.sign(id).unwrap();
28 | //! let blinding = Blinding::new(b"");
29 | //! let blinded_token = token - &blinding;
30 | //!
31 | //! let timestamp = [0x00, 0x05, 0xc0, 0xba, 0xea, 0x9c, 0x82, 0xb0];
32 | //!
33 | //! match Proof::new(&blinded_token, &[blinding], id, ×tamp, thread_rng()) {
34 | //! None => panic!(""),
35 | //! Some(proof) => {
36 | //! assert_eq!(proof.open(pk, id, ×tamp).unwrap_u8(), 1u8);
37 | //! }
38 | //! }
39 | //! ```
40 | //!
41 | //! This crate supports no-std by default. As such this means only 2 additional factors
42 | //! can be used. This is usually not a problem since 3FA is good enough. If you need
43 | //! more than 3FA (what security context are in???) this can be done with some work
44 | //! as is described below.
45 | //!
46 | //! Blinding factors are applied to tokens as follows
47 | //!
48 | //! ```
49 | //! use oberon::{SecretKey, Token, Blinding};
50 | //!
51 | //! let sk = SecretKey::hash(b"my super secret key seed");
52 | //! let token = sk.sign(b"test identity").unwrap();
53 | //!
54 | //! let blinded_token = token - Blinding::new(b"");
55 | //! let blinded_token = blinded_token - Blinding::new(b"");
56 | //! let blinded_token = blinded_token - Blinding::new(b"");
57 | //! ```
58 | //!
59 | //! It is important that the blindings are subtracted and not added since addition is used
60 | //! by `Proof::new`
61 | //!
62 | //! This scenario uses 3 extra blindings. In no-std mode, only two can be passed
63 | //! to `Proof::new`. In order to apply the third and still have this work, you simply
64 | //! reverse all but two of the blindings by adding them back in.
65 | //! This restriction doesn't apply when `alloc` or `std` features are used.
66 | //!
67 | //! This crate also supports compiling to wasm. Make sure to use --features=wasm
68 | //! to get the necessary functions
69 |
70 | #![no_std]
71 | #![deny(
72 | warnings,
73 | missing_docs,
74 | unused_import_braces,
75 | unused_qualifications,
76 | trivial_casts,
77 | trivial_numeric_casts
78 | )]
79 | #![cfg_attr(docsrs, feature(doc_cfg))]
80 |
81 | #[cfg(feature = "alloc")]
82 | extern crate alloc;
83 | #[cfg(feature = "std")]
84 | extern crate std;
85 |
86 | #[cfg(feature = "wasm")]
87 | macro_rules! wasm_slice_impl {
88 | ($name:ident) => {
89 | impl wasm_bindgen::describe::WasmDescribe for $name {
90 | fn describe() {
91 | wasm_bindgen::describe::inform(wasm_bindgen::describe::SLICE)
92 | }
93 | }
94 |
95 | impl wasm_bindgen::convert::IntoWasmAbi for $name {
96 | type Abi = wasm_bindgen::convert::WasmSlice;
97 |
98 | fn into_abi(self) -> Self::Abi {
99 | let a = self.to_bytes();
100 | Self::Abi {
101 | ptr: a.as_ptr().into_abi(),
102 | len: a.len() as u32,
103 | }
104 | }
105 | }
106 |
107 | impl wasm_bindgen::convert::FromWasmAbi for $name {
108 | type Abi = wasm_bindgen::convert::WasmSlice;
109 |
110 | #[inline]
111 | unsafe fn from_abi(js: Self::Abi) -> Self {
112 | use core::{convert::TryFrom, slice};
113 |
114 | let ptr = <*mut u8>::from_abi(js.ptr);
115 | let len = js.len as usize;
116 | let r = slice::from_raw_parts(ptr, len);
117 |
118 | match <[u8; $name::BYTES]>::try_from(r) {
119 | Ok(d) => $name::from_bytes(&d).unwrap(),
120 | Err(_) => Self::default(),
121 | }
122 | }
123 | }
124 |
125 | impl wasm_bindgen::convert::OptionIntoWasmAbi for $name {
126 | fn none() -> wasm_bindgen::convert::WasmSlice {
127 | wasm_bindgen::convert::WasmSlice { ptr: 0, len: 0 }
128 | }
129 | }
130 |
131 | impl wasm_bindgen::convert::OptionFromWasmAbi for $name {
132 | fn is_none(slice: &wasm_bindgen::convert::WasmSlice) -> bool {
133 | slice.ptr == 0
134 | }
135 | }
136 |
137 | impl TryFrom for $name {
138 | type Error = &'static str;
139 |
140 | fn try_from(value: wasm_bindgen::JsValue) -> Result {
141 | serde_json::from_str::<$name>(&value.as_string().unwrap())
142 | .map_err(|_| "unable to deserialize value")
143 | }
144 | }
145 | };
146 | }
147 |
148 | mod blinding;
149 | #[cfg(feature = "ffi")]
150 | mod ffi;
151 | #[cfg(feature = "php")]
152 | mod php;
153 | mod proof;
154 | mod public_key;
155 | #[cfg(feature = "python")]
156 | mod python;
157 | mod secret_key;
158 | mod token;
159 | mod util;
160 | #[cfg(feature = "wasm")]
161 | mod web;
162 |
163 | #[cfg(not(any(feature = "rust", feature = "alloc", feature = "blstrs_plus")))]
164 | compile_error!("Please select bls12_381_plus or blstrs_plus as your elliptic curve");
165 |
166 | /// The inner representation types
167 | pub mod inner_types {
168 | #[cfg(not(feature = "std"))]
169 | pub use bls12_381_plus::{
170 | elliptic_curve,
171 | ff::{Field, PrimeField},
172 | group::{self, Curve, Group, GroupEncoding, prime::PrimeCurveAffine},
173 | *
174 | };
175 | #[cfg(feature = "std")]
176 | pub use blstrs_plus::{
177 | elliptic_curve,
178 | ff::{Field, PrimeField},
179 | group::{self, Curve, Group, GroupEncoding, prime::PrimeCurveAffine},
180 | pairing_lib::{self, MillerLoopResult, MultiMillerLoop},
181 | *
182 | };
183 | }
184 |
185 | pub use blinding::*;
186 | #[cfg_attr(docsrs, doc(cfg(feature = "ffi")))]
187 | #[cfg(feature = "ffi")]
188 | pub use ffi::*;
189 | #[cfg_attr(docsrs, doc(cfg(feature = "php")))]
190 | #[cfg(feature = "php")]
191 | pub use php::*;
192 | pub use proof::*;
193 | pub use public_key::*;
194 | #[cfg_attr(docsrs, doc(cfg(feature = "python")))]
195 | #[cfg(feature = "python")]
196 | pub use python::*;
197 | pub use secret_key::*;
198 | pub use token::*;
199 | #[cfg_attr(docsrs, doc(cfg(feature = "wasm")))]
200 | #[cfg(feature = "wasm")]
201 | pub use web::*;
202 |
--------------------------------------------------------------------------------
/rust/src/php.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | use crate::{Blinding, Proof, PublicKey, SecretKey, Token};
6 | #[cfg_attr(windows, feature(abi_vectorcall))]
7 | use ext_php_rs::prelude::*;
8 | use rand::thread_rng;
9 |
10 | use ext_php_rs::zend::ModuleEntry;
11 | use ext_php_rs::{info_table_end, info_table_row, info_table_start};
12 | use std::{convert::TryFrom, string::String, vec::Vec};
13 |
14 | /// Create a new secret key
15 | ///
16 | /// @return string The secret key
17 | #[php_function]
18 | pub fn oberon_new_secret_key() -> Vec {
19 | let rng = thread_rng();
20 | SecretKey::new(rng).to_bytes().to_vec()
21 | }
22 |
23 | /// Get the public key from the secret key
24 | ///
25 | /// @param string $sk The secret key
26 | ///
27 | /// @return string The public key
28 | #[php_function]
29 | pub fn oberon_get_public_key(sk: Vec) -> Option> {
30 | match secret_key(sk) {
31 | None => None,
32 | Some(sk) => Some(PublicKey::from(&sk).to_bytes().to_vec()),
33 | }
34 | }
35 |
36 | /// Create new secret key from a seed
37 | ///
38 | /// @param string seed a binary string from which to derive the secret key
39 | ///
40 | /// @return string The secret key
41 | #[php_function]
42 | pub fn oberon_secret_key_from_seed(seed: Vec) -> Vec {
43 | SecretKey::hash(&seed).to_bytes().to_vec()
44 | }
45 |
46 | /// Create a new token for a given ID
47 | ///
48 | /// @param string $sk The secret key
49 | /// @param string $id The identifier
50 | ///
51 | /// @return string|null The token if successful, null on failure
52 | #[php_function]
53 | pub fn oberon_new_token(sk: Vec, id: String) -> Option> {
54 | match secret_key(sk) {
55 | None => None,
56 | Some(sk) => Some(Token::new(&sk, id.as_bytes()).unwrap().to_bytes().to_vec()),
57 | }
58 | }
59 |
60 | /// Verify a token for a given ID
61 | ///
62 | /// @param string $token The token
63 | /// @param string $pk The public key
64 | /// @param string $id The identifier
65 | ///
66 | /// @param bool result of check
67 | #[php_function]
68 | pub fn oberon_verify_token(token: Vec, pk: Vec, id: String) -> bool {
69 | match (get_token(token), public_key(pk)) {
70 | (Some(t), Some(k)) => t.verify(k, id.as_bytes()).unwrap_u8() == 1,
71 | (_, _) => false,
72 | }
73 | }
74 |
75 | /// Create a blinding factor from the specified data
76 | ///
77 | /// @param string $data an arbitrary byte sequence
78 | ///
79 | /// @param string The blinding factor
80 | #[php_function]
81 | pub fn oberon_create_blinding(data: Vec) -> Vec {
82 | Blinding::new(&data).to_bytes().to_vec()
83 | }
84 |
85 | /// Adds a blinding factor to the token
86 | ///
87 | /// @param string $token The token
88 | /// @param string $data an arbitrary byte sequence
89 | ///
90 | /// @param string|null The blinding factor or null if invalid token
91 | #[php_function]
92 | pub fn oberon_add_blinding(token: Vec, data: Vec) -> Option> {
93 | match get_token(token) {
94 | None => None,
95 | Some(t) => {
96 | let val = t - Blinding::new(&data);
97 | Some(val.to_bytes().to_vec())
98 | }
99 | }
100 | }
101 |
102 | /// Removes a blinding factor to the token
103 | ///
104 | /// @param string $token The token
105 | /// @param string $data an arbitrary byte sequence
106 | ///
107 | /// @param string The blinding factor or null if invalid token
108 | #[php_function]
109 | pub fn oberon_remove_blinding(token: Vec, data: Vec) -> Option> {
110 | match get_token(token) {
111 | None => None,
112 | Some(t) => {
113 | let val = t + Blinding::new(&data);
114 | Some(val.to_bytes().to_vec())
115 | }
116 | }
117 | }
118 |
119 | /// Creates a proof using a nonce received from a verifier
120 | ///
121 | /// @param string $token The token
122 | /// @param string $id The identifier
123 | /// @param array[string] $blindings The blinding factors
124 | /// @param string $nonce The proof nonce
125 | ///
126 | /// @return string|null The proof is successful, null on failure
127 | #[php_function]
128 | pub fn oberon_create_proof(
129 | token: Vec,
130 | id: String,
131 | blindings: Vec>,
132 | nonce: Vec,
133 | ) -> Option> {
134 | let bs: Vec = blindings.iter().map(|b| Blinding::new(b)).collect();
135 |
136 | let rng = thread_rng();
137 | match get_token(token) {
138 | None => None,
139 | Some(t) => Proof::new(&t, &bs, id.as_bytes(), nonce, rng).map(|p| p.to_bytes().to_vec()),
140 | }
141 | }
142 |
143 | /// Creates a proof using a nonce received from a verifier
144 | ///
145 | /// @param string $proof The proof
146 | /// @param string $pk The public key
147 | /// @param string $id The identifier
148 | /// @param string $nonce The proof nonce
149 | ///
150 | /// @return bool The check proof result
151 | #[php_function]
152 | pub fn oberon_verify_proof(proof: Vec, pk: Vec, id: String, nonce: Vec) -> bool {
153 | match (get_proof(proof), public_key(pk)) {
154 | (Some(p), Some(k)) => p.open(k, id.as_bytes(), nonce).unwrap_u8() == 1,
155 | (_, _) => false,
156 | }
157 | }
158 |
159 | macro_rules! from_bytes {
160 | ($name:ident, $type:ident) => {
161 | fn $name(input: Vec) -> Option<$type> {
162 | match <[u8; $type::BYTES]>::try_from(input.as_slice()) {
163 | Err(_) => None,
164 | Ok(bytes) => {
165 | let val = $type::from_bytes(&bytes);
166 | if val.is_some().unwrap_u8() == 1u8 {
167 | Some(val.unwrap())
168 | } else {
169 | None
170 | }
171 | }
172 | }
173 | }
174 | };
175 | }
176 | from_bytes!(secret_key, SecretKey);
177 | from_bytes!(public_key, PublicKey);
178 | from_bytes!(get_token, Token);
179 | from_bytes!(get_proof, Proof);
180 |
181 | /// The php init function
182 | #[no_mangle]
183 | pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
184 | info_table_start!();
185 | info_table_row!("oberon extension", "enabled");
186 | info_table_end!();
187 | }
188 |
189 | /// Boilerplate function
190 | #[php_module]
191 | pub fn module(module: ModuleBuilder) -> ModuleBuilder {
192 | module.info_function(php_module_info)
193 | }
194 |
--------------------------------------------------------------------------------
/rust/src/proof.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | use crate::inner_types::*;
6 | use crate::{util::*, Blinding, PublicKey, Token};
7 | use rand_core::{CryptoRng, RngCore};
8 | use serde::{Deserialize, Serialize};
9 | use subtle::{Choice, CtOption};
10 |
11 | /// A zero-knowledge proof of a valid token
12 | #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)]
13 | pub struct Proof {
14 | u: G1Projective,
15 | z: G1Projective,
16 | }
17 |
18 | #[cfg(feature = "wasm")]
19 | wasm_slice_impl!(Proof);
20 |
21 | impl Proof {
22 | /// The number of bytes in a proof
23 | pub const BYTES: usize = 96;
24 |
25 | /// Create a new ZKP based proof
26 | ///
27 | /// Works like this
28 | ///
29 | /// m = H_s(id)
30 | /// m' = H_s(m)
31 | /// A = H_G(m')
32 | /// r = random scalar
33 | /// t = random scalar or H_s(A || timestamp)
34 | /// U = r.A
35 | /// Z = -(r + t).(\sigma + blindings)
36 | ///
37 | /// Verified
38 | /// e(U + t.A, W + m.X + m'.Y).e(Z, P) == 1
39 | pub fn new, N: AsRef<[u8]>>(
40 | token: &Token,
41 | blindings: &[Blinding],
42 | id: B,
43 | nonce: N,
44 | mut rng: impl RngCore + CryptoRng,
45 | ) -> Option {
46 | let id = id.as_ref();
47 | let m = hash_to_scalar(&[id]);
48 | if m.is_zero().unwrap_u8() == 1 {
49 | return None;
50 | }
51 | let m_tick = hash_to_scalar(&[&m.to_le_bytes()[..]]);
52 | if m_tick.is_zero().unwrap_u8() == 1 {
53 | return None;
54 | }
55 | let a = hash_to_curve(&m_tick.to_le_bytes()[..]);
56 | if a.is_identity().unwrap_u8() == 1 {
57 | return None;
58 | }
59 |
60 | let r = gen_nonz_rnd_scalar(&mut rng);
61 | let u = a * r;
62 | let t = hash_to_scalar(&[&u.to_affine().to_compressed(), nonce.as_ref()]);
63 |
64 | let z: G1Projective =
65 | (token.0 + blindings.iter().map(|b| b.0).sum::()) * (r + t);
66 | Some(Self { u, z: -z })
67 | }
68 |
69 | /// Check whether this proof is valid
70 | pub fn open, N: AsRef<[u8]>>(&self, pk: PublicKey, id: B, nonce: N) -> Choice {
71 | if (self.u.is_identity() | self.z.is_identity() | pk.is_invalid()).unwrap_u8() == 1u8 {
72 | return 0u8.into();
73 | }
74 |
75 | let id = id.as_ref();
76 | let m = hash_to_scalar(&[id]);
77 | if m.is_zero().unwrap_u8() == 1 {
78 | return 0u8.into();
79 | }
80 | let m_tick = hash_to_scalar(&[&m.to_le_bytes()[..]]);
81 | if m_tick.is_zero().unwrap_u8() == 1u8 {
82 | return 0u8.into();
83 | }
84 | let a = hash_to_curve(&m_tick.to_le_bytes()[..]);
85 | if a.is_identity().unwrap_u8() == 1 {
86 | return 0u8.into();
87 | }
88 |
89 | let t = hash_to_scalar(&[&self.u.to_affine().to_compressed(), nonce.as_ref()]);
90 |
91 | let u = a * t + self.u;
92 | #[cfg(feature = "std")]
93 | let rhs = G2Projective::sum_of_products(
94 | &[pk.w, pk.x, pk.y],
95 | &[m_tick, Scalar::ONE, m],
96 | );
97 | #[cfg(all(feature = "rust", not(feature = "std")))]
98 | let rhs = G2Projective::sum_of_products_in_place(
99 | &[pk.w, pk.x, pk.y],
100 | &mut [m_tick, Scalar::ONE, m],
101 | );
102 |
103 | multi_miller_loop(&[
104 | (&u.to_affine(), &G2Prepared::from(rhs.to_affine())),
105 | (
106 | &self.z.to_affine(),
107 | &G2Prepared::from(G2Affine::generator()),
108 | ),
109 | ])
110 | .final_exponentiation()
111 | .is_identity()
112 | }
113 |
114 | /// Convert this proof into a byte sequence
115 | pub fn to_bytes(&self) -> [u8; Self::BYTES] {
116 | let mut out = [0u8; Self::BYTES];
117 | out[..48].copy_from_slice(&self.u.to_affine().to_compressed());
118 | out[48..].copy_from_slice(&self.z.to_affine().to_compressed());
119 | out
120 | }
121 |
122 | /// Convert a byte sequence to a proof
123 | pub fn from_bytes(data: &[u8; Self::BYTES]) -> CtOption {
124 | let uu = G1Affine::from_compressed(&<[u8; 48]>::try_from(&data[..48]).unwrap())
125 | .map(G1Projective::from);
126 | let zz = G1Affine::from_compressed(&<[u8; 48]>::try_from(&data[48..]).unwrap())
127 | .map(G1Projective::from);
128 |
129 | uu.and_then(|u| zz.and_then(|z| CtOption::new(Proof { u, z }, 1u8.into())))
130 | }
131 | }
132 |
133 | fn gen_nonz_rnd_scalar(mut rng: impl RngCore + CryptoRng) -> Scalar {
134 | let mut s = Scalar::random(&mut rng);
135 | while s.is_zero().unwrap_u8() == 1 || s == Scalar::ONE {
136 | s = Scalar::random(&mut rng);
137 | }
138 | s
139 | }
140 |
141 | #[test]
142 | fn eproof_works() {
143 | let sk = crate::SecretKey::new(rand::thread_rng());
144 | let pk = PublicKey::from(&sk);
145 | let id = b"eproof_works";
146 | let token = sk.sign(id).unwrap();
147 |
148 | let blinding = Blinding::new(b"1234");
149 | let blind_token = token.clone() - blinding;
150 |
151 | let nonce = b"eproof_works_nonce";
152 |
153 | let opt_eproof = Proof::new(&token, &[], id, nonce, rand::thread_rng());
154 | assert!(opt_eproof.is_some());
155 | let mut eproof = opt_eproof.unwrap();
156 | assert_eq!(eproof.open(pk, id, nonce).unwrap_u8(), 1u8);
157 |
158 | let t = Scalar::random(rand::thread_rng());
159 | eproof.u *= t;
160 | eproof.z *= t;
161 | assert_eq!(eproof.open(pk, id, nonce).unwrap_u8(), 0u8);
162 |
163 | let opt_eproof = Proof::new(&blind_token, &[blinding], id, nonce, rand::thread_rng());
164 | assert!(opt_eproof.is_some());
165 | let mut eproof = opt_eproof.unwrap();
166 | assert_eq!(eproof.open(pk, id, nonce).unwrap_u8(), 1u8);
167 |
168 | let t = Scalar::random(rand::thread_rng());
169 | eproof.u *= t;
170 | eproof.z *= t;
171 | assert_eq!(eproof.open(pk, id, nonce).unwrap_u8(), 0u8);
172 | }
173 |
--------------------------------------------------------------------------------
/rust/src/public_key.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | use crate::inner_types::*;
6 | use crate::{SecretKey, Token};
7 | use core::convert::TryFrom;
8 | use serde::{Deserialize, Serialize};
9 | use subtle::{Choice, CtOption};
10 |
11 | /// The public key used for verifying tokens
12 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
13 | pub struct PublicKey {
14 | pub(crate) w: G2Projective,
15 | pub(crate) x: G2Projective,
16 | pub(crate) y: G2Projective,
17 | }
18 |
19 | impl Default for PublicKey {
20 | fn default() -> Self {
21 | Self {
22 | w: G2Projective::IDENTITY,
23 | x: G2Projective::IDENTITY,
24 | y: G2Projective::IDENTITY,
25 | }
26 | }
27 | }
28 |
29 | impl From<&SecretKey> for PublicKey {
30 | fn from(sk: &SecretKey) -> Self {
31 | Self {
32 | w: G2Projective::GENERATOR * sk.w,
33 | x: G2Projective::GENERATOR * sk.x,
34 | y: G2Projective::GENERATOR * sk.y,
35 | }
36 | }
37 | }
38 |
39 | #[cfg(feature = "wasm")]
40 | wasm_slice_impl!(PublicKey);
41 |
42 | impl PublicKey {
43 | /// The number of bytes in a public key
44 | pub const BYTES: usize = 288;
45 |
46 | /// Is this public key invalid
47 | pub fn is_invalid(&self) -> Choice {
48 | self.w.is_identity() | self.y.is_identity() | self.x.is_identity()
49 | }
50 |
51 | /// Convert this public key into a byte sequence
52 | pub fn to_bytes(&self) -> [u8; Self::BYTES] {
53 | let mut out = [0u8; Self::BYTES];
54 | out[0..96].copy_from_slice(&self.w.to_affine().to_compressed()[..]);
55 | out[96..192].copy_from_slice(&self.x.to_affine().to_compressed()[..]);
56 | out[192..288].copy_from_slice(&self.y.to_affine().to_compressed()[..]);
57 | out
58 | }
59 |
60 | /// Convert a byte sequence to a public key
61 | pub fn from_bytes(data: &[u8; Self::BYTES]) -> CtOption {
62 | let ww = G2Affine::from_compressed(&<[u8; 96]>::try_from(&data[..96]).unwrap())
63 | .map(G2Projective::from);
64 | let xx = G2Affine::from_compressed(&<[u8; 96]>::try_from(&data[96..192]).unwrap())
65 | .map(G2Projective::from);
66 | let yy = G2Affine::from_compressed(&<[u8; 96]>::try_from(&data[192..]).unwrap())
67 | .map(G2Projective::from);
68 |
69 | ww.and_then(|w| {
70 | xx.and_then(|x| yy.and_then(|y| CtOption::new(Self { w, x, y }, Choice::from(1u8))))
71 | })
72 | }
73 |
74 | /// Verify that a token is valid
75 | pub fn verify_token>(&self, id: B, token: &Token) -> Choice {
76 | token.verify(*self, id)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/rust/src/python.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | use crate::{Blinding, Proof, PublicKey, SecretKey, Token};
6 | use pyo3::{exceptions::PyValueError, prelude::*};
7 | use rand::thread_rng;
8 |
9 | use std::{convert::TryFrom, vec::Vec};
10 |
11 | /// Create a new secret key
12 | ///
13 | /// @return string The secret key
14 | #[pyfunction]
15 | pub fn new_secret_key() -> Vec {
16 | let rng = thread_rng();
17 | SecretKey::new(rng).to_bytes().to_vec()
18 | }
19 |
20 | /// Get the public key from the secret key
21 | #[pyfunction]
22 | pub fn get_public_key(sk: Vec) -> PyResult> {
23 | match secret_key(sk) {
24 | None => Err(PyValueError::new_err("Invalid secret key")),
25 | Some(sk) => Ok(PublicKey::from(&sk).to_bytes().to_vec()),
26 | }
27 | }
28 |
29 | /// Create new secret key from a seed
30 | #[pyfunction]
31 | pub fn secret_key_from_seed(seed: Vec) -> Vec {
32 | SecretKey::hash(&seed).to_bytes().to_vec()
33 | }
34 |
35 | /// Create a new token for a given ID
36 | ///
37 | /// @param string $sk The secret key
38 | /// @param string $id The identifier
39 | ///
40 | /// @return string|null The token if successful, null on failure
41 | #[pyfunction]
42 | pub fn new_token(sk: Vec, id: Vec) -> PyResult> {
43 | match secret_key(sk) {
44 | None => Err(PyValueError::new_err("Invalid secret key")),
45 | Some(sk) => Ok(Token::new(&sk, &id).unwrap().to_bytes().to_vec()),
46 | }
47 | }
48 |
49 | /// Verify a token for a given ID
50 | ///
51 | /// @param string $token The token
52 | /// @param string $pk The public key
53 | /// @param string $id The identifier
54 | ///
55 | /// @param bool result of check
56 | #[pyfunction]
57 | pub fn verify_token(token: Vec, pk: Vec, id: Vec) -> bool {
58 | match (get_token(token), public_key(pk)) {
59 | (Some(t), Some(k)) => t.verify(k, &id).unwrap_u8() == 1,
60 | (_, _) => false,
61 | }
62 | }
63 |
64 | /// Create a blinding factor from the specified data
65 | ///
66 | /// @param string $data an arbitrary byte sequence
67 | ///
68 | /// @param string The blinding factor
69 | #[pyfunction]
70 | pub fn create_blinding(data: Vec) -> Vec {
71 | Blinding::new(&data).to_bytes().to_vec()
72 | }
73 |
74 | /// Adds a blinding factor to the token
75 | ///
76 | /// @param string $token The token
77 | /// @param string $data an arbitrary byte sequence
78 | ///
79 | /// @param string|null The blinding factor or null if invalid token
80 | #[pyfunction]
81 | pub fn add_blinding(token: Vec, data: Vec) -> PyResult> {
82 | match get_token(token) {
83 | None => Err(PyValueError::new_err("Invalid token")),
84 | Some(t) => {
85 | let val = t - Blinding::new(&data);
86 | Ok(val.to_bytes().to_vec())
87 | }
88 | }
89 | }
90 |
91 | /// Removes a blinding factor to the token
92 | ///
93 | /// @param string $token The token
94 | /// @param string $data an arbitrary byte sequence
95 | ///
96 | /// @param string The blinding factor or null if invalid token
97 | #[pyfunction]
98 | pub fn remove_blinding(token: Vec, data: Vec) -> PyResult> {
99 | match get_token(token) {
100 | None => Err(PyValueError::new_err("Invalid token")),
101 | Some(t) => {
102 | let val = t + Blinding::new(&data);
103 | Ok(val.to_bytes().to_vec())
104 | }
105 | }
106 | }
107 |
108 | /// Creates a proof using a nonce received from a verifier
109 | ///
110 | /// @param string $token The token
111 | /// @param string $id The identifier
112 | /// @param array[string] $blindings The blinding factors
113 | /// @param string $nonce The proof nonce
114 | ///
115 | /// @return string|null The proof is successful, null on failure
116 | #[pyfunction]
117 | pub fn create_proof(
118 | token: Vec,
119 | id: Vec,
120 | blindings: Vec>,
121 | nonce: Vec,
122 | ) -> PyResult> {
123 | let bs: Vec = blindings.iter().map(|b| Blinding::new(b)).collect();
124 |
125 | let rng = thread_rng();
126 | match get_token(token) {
127 | None => Err(PyValueError::new_err("Invalid token")),
128 | Some(t) => Proof::new(&t, &bs, id, nonce, rng)
129 | .map(|p| p.to_bytes().to_vec())
130 | .ok_or_else(|| PyValueError::new_err("")),
131 | }
132 | }
133 |
134 | /// Creates a proof using a nonce received from a verifier
135 | ///
136 | /// @param string $proof The proof
137 | /// @param string $pk The public key
138 | /// @param string $id The identifier
139 | /// @param string $nonce The proof nonce
140 | ///
141 | /// @return bool The check proof result
142 | #[pyfunction]
143 | pub fn verify_proof(proof: Vec, pk: Vec, id: Vec, nonce: Vec) -> bool {
144 | match (get_proof(proof), public_key(pk)) {
145 | (Some(p), Some(k)) => p.open(k, id, nonce).unwrap_u8() == 1,
146 | (_, _) => false,
147 | }
148 | }
149 |
150 | macro_rules! from_bytes {
151 | ($name:ident, $type:ident) => {
152 | fn $name(input: Vec) -> Option<$type> {
153 | match <[u8; $type::BYTES]>::try_from(input.as_slice()) {
154 | Err(_) => None,
155 | Ok(bytes) => {
156 | let val = $type::from_bytes(&bytes);
157 | if val.is_some().unwrap_u8() == 1u8 {
158 | Some(val.unwrap())
159 | } else {
160 | None
161 | }
162 | }
163 | }
164 | }
165 | };
166 | }
167 | from_bytes!(secret_key, SecretKey);
168 | from_bytes!(public_key, PublicKey);
169 | from_bytes!(get_token, Token);
170 | from_bytes!(get_proof, Proof);
171 |
172 | /// Boilerplate function
173 | #[pymodule]
174 | pub fn oberon(_py: Python, m: &PyModule) -> PyResult<()> {
175 | m.add_function(wrap_pyfunction!(new_secret_key, m)?)?;
176 | m.add_function(wrap_pyfunction!(get_public_key, m)?)?;
177 | m.add_function(wrap_pyfunction!(secret_key_from_seed, m)?)?;
178 | m.add_function(wrap_pyfunction!(new_token, m)?)?;
179 | m.add_function(wrap_pyfunction!(verify_token, m)?)?;
180 | m.add_function(wrap_pyfunction!(create_blinding, m)?)?;
181 | m.add_function(wrap_pyfunction!(add_blinding, m)?)?;
182 | m.add_function(wrap_pyfunction!(remove_blinding, m)?)?;
183 | m.add_function(wrap_pyfunction!(create_proof, m)?)?;
184 | m.add_function(wrap_pyfunction!(verify_proof, m)?)?;
185 | Ok(())
186 | }
187 |
--------------------------------------------------------------------------------
/rust/src/secret_key.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | use crate::inner_types::*;
6 | use crate::{util::*, Token};
7 | use core::convert::TryFrom;
8 | use rand_core::*;
9 | use serde::{Deserialize, Serialize};
10 | use subtle::{Choice, ConstantTimeEq, CtOption};
11 | use zeroize::ZeroizeOnDrop;
12 |
13 | /// The secret key used for signing tokens
14 | /// Display is not implemented to prevent accidental leak of the key
15 | ///
16 | /// To generate a random secret key, select a random number generator
17 | /// to pass to `new`
18 | ///
19 | /// ```
20 | /// use oberon::*;
21 | /// let sk = SecretKey::new(rand::thread_rng());
22 | /// ```
23 | ///
24 | /// or to generate a secret key from a known seed
25 | ///
26 | /// ```
27 | /// use oberon::*;
28 | /// let sk = SecretKey::hash(b"my seed");
29 | /// ```
30 | #[derive(Clone, Debug, Eq, Deserialize, Serialize, ZeroizeOnDrop)]
31 | #[zeroize(drop)]
32 | pub struct SecretKey {
33 | pub(crate) w: Scalar,
34 | pub(crate) x: Scalar,
35 | pub(crate) y: Scalar,
36 | }
37 |
38 | impl Default for SecretKey {
39 | fn default() -> Self {
40 | Self {
41 | w: Scalar::ZERO,
42 | x: Scalar::ZERO,
43 | y: Scalar::ZERO,
44 | }
45 | }
46 | }
47 |
48 | impl From<&[u8; SecretKey::BYTES]> for SecretKey {
49 | fn from(data: &[u8; Self::BYTES]) -> Self {
50 | Self::from_bytes(data).unwrap()
51 | }
52 | }
53 |
54 | impl PartialEq for SecretKey {
55 | fn eq(&self, other: &Self) -> bool {
56 | self.ct_eq(other).unwrap_u8() == 1
57 | }
58 | }
59 |
60 | impl ConstantTimeEq for SecretKey {
61 | fn ct_eq(&self, rhs: &Self) -> Choice {
62 | self.x.ct_eq(&rhs.x) & self.y.ct_eq(&rhs.y) & self.w.ct_eq(&rhs.w)
63 | }
64 | }
65 |
66 | #[cfg(feature = "wasm")]
67 | wasm_slice_impl!(SecretKey);
68 |
69 | impl SecretKey {
70 | /// The number of bytes in a secret key
71 | pub const BYTES: usize = 96;
72 |
73 | /// Generate a new random key
74 | pub fn new(mut rng: impl RngCore + CryptoRng) -> Self {
75 | Self {
76 | w: Scalar::random(&mut rng),
77 | x: Scalar::random(&mut rng),
78 | y: Scalar::random(&mut rng),
79 | }
80 | }
81 |
82 | /// Generate a new key from a seed using SHAKE-256
83 | pub fn hash(data: &[u8]) -> Self {
84 | let mut values = [Scalar::ZERO; 3];
85 | hash_to_scalars(&[data], &mut values);
86 | Self {
87 | w: values[0],
88 | x: values[1],
89 | y: values[2],
90 | }
91 | }
92 |
93 | /// Convert this secret key into a byte sequence
94 | pub fn to_bytes(&self) -> [u8; Self::BYTES] {
95 | let mut out = [0u8; Self::BYTES];
96 | out[..32].copy_from_slice(&self.w.to_le_bytes()[..]);
97 | out[32..64].copy_from_slice(&self.x.to_le_bytes()[..]);
98 | out[64..].copy_from_slice(&self.y.to_le_bytes()[..]);
99 | out
100 | }
101 |
102 | /// Convert a byte sequence to a secret key
103 | pub fn from_bytes(data: &[u8; Self::BYTES]) -> CtOption {
104 | let ww = Scalar::from_le_bytes(&<[u8; 32]>::try_from(&data[..32]).unwrap());
105 | let xx = Scalar::from_le_bytes(&<[u8; 32]>::try_from(&data[32..64]).unwrap());
106 | let yy = Scalar::from_le_bytes(&<[u8; 32]>::try_from(&data[64..]).unwrap());
107 |
108 | ww.and_then(|w| {
109 | xx.and_then(|x| yy.and_then(|y| CtOption::new(Self { w, x, y }, Choice::from(1u8))))
110 | })
111 | }
112 |
113 | /// Sign an `id` to a token
114 | pub fn sign>(&self, id: B) -> Option {
115 | Token::new(self, id)
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/rust/src/token.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | use crate::inner_types::*;
6 | use crate::{util::*, Blinding, PublicKey, SecretKey};
7 | #[cfg(feature = "wasm")]
8 | use core::convert::TryFrom;
9 | use core::ops::{Add, Sub};
10 | use serde::{Deserialize, Serialize};
11 | use subtle::{Choice, ConstantTimeEq, CtOption};
12 | use zeroize::Zeroize;
13 |
14 | /// The authentication token
15 | /// Display is not implemented to prevent accidental leak of the token
16 | #[derive(Clone, Debug, Eq, Deserialize, Serialize)]
17 | pub struct Token(pub(crate) G1Projective);
18 |
19 | impl Zeroize for Token {
20 | fn zeroize(&mut self) {
21 | self.0 = G1Projective::IDENTITY;
22 | }
23 | }
24 |
25 | impl Drop for Token {
26 | fn drop(&mut self) {
27 | self.zeroize();
28 | }
29 | }
30 |
31 | impl Default for Token {
32 | fn default() -> Self {
33 | Self(G1Projective::IDENTITY)
34 | }
35 | }
36 |
37 | impl PartialEq for Token {
38 | fn eq(&self, other: &Self) -> bool {
39 | self.ct_eq(other).unwrap_u8() == 1
40 | }
41 | }
42 |
43 | impl ConstantTimeEq for Token {
44 | fn ct_eq(&self, other: &Self) -> Choice {
45 | self.0.ct_eq(&other.0)
46 | }
47 | }
48 |
49 | #[cfg(feature = "wasm")]
50 | wasm_slice_impl!(Token);
51 |
52 | impl<'a, 'b> Add<&'b Blinding> for &'a Token {
53 | type Output = Token;
54 |
55 | #[inline]
56 | fn add(self, rhs: &'b Blinding) -> Token {
57 | self + *rhs
58 | }
59 | }
60 |
61 | impl<'b> Add<&'b Blinding> for Token {
62 | type Output = Token;
63 |
64 | #[inline]
65 | fn add(self, rhs: &'b Blinding) -> Token {
66 | self + *rhs
67 | }
68 | }
69 |
70 | impl<'a> Add for &'a Token {
71 | type Output = Token;
72 |
73 | #[inline]
74 | fn add(self, rhs: Blinding) -> Token {
75 | Token(self.0 + rhs.0)
76 | }
77 | }
78 |
79 | impl Add for Token {
80 | type Output = Token;
81 |
82 | #[inline]
83 | fn add(self, rhs: Blinding) -> Token {
84 | Token(self.0 + rhs.0)
85 | }
86 | }
87 |
88 | impl<'a, 'b> Sub<&'b Blinding> for &'a Token {
89 | type Output = Token;
90 |
91 | #[inline]
92 | fn sub(self, rhs: &'b Blinding) -> Token {
93 | self - *rhs
94 | }
95 | }
96 |
97 | impl<'b> Sub<&'b Blinding> for Token {
98 | type Output = Token;
99 |
100 | #[inline]
101 | fn sub(self, rhs: &'b Blinding) -> Token {
102 | self - *rhs
103 | }
104 | }
105 |
106 | impl<'a> Sub for &'a Token {
107 | type Output = Token;
108 |
109 | #[inline]
110 | fn sub(self, rhs: Blinding) -> Token {
111 | Token(self.0 - rhs.0)
112 | }
113 | }
114 |
115 | impl Sub for Token {
116 | type Output = Token;
117 |
118 | #[inline]
119 | fn sub(self, rhs: Blinding) -> Token {
120 | Token(self.0 - rhs.0)
121 | }
122 | }
123 |
124 | impl Token {
125 | /// The number of bytes in a token
126 | pub const BYTES: usize = 48;
127 |
128 | /// Create a new token
129 | pub fn new>(sk: &SecretKey, id: B) -> Option {
130 | let id = id.as_ref();
131 | let m = hash_to_scalar(&[id]);
132 | if m.is_zero().unwrap_u8() == 1 {
133 | return None;
134 | }
135 | let m_tick = hash_to_scalar(&[&m.to_le_bytes()[..]]);
136 | if m_tick.is_zero().unwrap_u8() == 1 {
137 | return None;
138 | }
139 | let u = hash_to_curve(&m_tick.to_le_bytes()[..]);
140 | if u.is_identity().unwrap_u8() == 1 {
141 | return None;
142 | }
143 |
144 | let sigma = u * (sk.x + sk.w * m_tick + sk.y * m);
145 | if sigma.is_identity().unwrap_u8() == 1 {
146 | return None;
147 | }
148 | Some(Self(sigma))
149 | }
150 |
151 | /// Check whether the token is valid to the public key
152 | pub fn verify>(&self, pk: PublicKey, id: B) -> Choice {
153 | let id = id.as_ref();
154 | let m = hash_to_scalar(&[id]);
155 | if m.is_zero().unwrap_u8() == 1 {
156 | return Choice::from(0u8);
157 | }
158 | let m_tick = hash_to_scalar(&[&m.to_le_bytes()[..]]);
159 | if m_tick.is_zero().unwrap_u8() == 1 {
160 | return Choice::from(0u8);
161 | }
162 | let u = hash_to_curve(&m_tick.to_le_bytes()[..]);
163 | if u.is_identity().unwrap_u8() == 1 {
164 | return Choice::from(0u8);
165 | }
166 |
167 | #[cfg(feature = "std")]
168 | let rhs = G2Projective::sum_of_products(
169 | &[pk.w, pk.x, pk.y],
170 | &[m_tick, Scalar::ONE, m]);
171 | #[cfg(all(feature = "rust", not(feature = "std")))]
172 | let rhs = G2Projective::sum_of_products_in_place(
173 | &[pk.w, pk.x, pk.y],
174 | &mut [m_tick, Scalar::ONE, m]);
175 |
176 | multi_miller_loop(&[
177 | (&u.to_affine(), &G2Prepared::from(rhs.to_affine())),
178 | (
179 | &self.0.to_affine(),
180 | &G2Prepared::from(-G2Affine::generator()),
181 | ),
182 | ])
183 | .final_exponentiation()
184 | .is_identity()
185 | }
186 |
187 | /// Convert this token into a byte sequence
188 | pub fn to_bytes(&self) -> [u8; Self::BYTES] {
189 | self.0.to_affine().to_compressed()
190 | }
191 |
192 | /// Convert a bytes sequence into a token
193 | pub fn from_bytes(data: &[u8; Self::BYTES]) -> CtOption {
194 | G1Affine::from_compressed(data).map(|p| Self(G1Projective::from(p)))
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/rust/src/util.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | use crate::inner_types::{elliptic_curve::hash2curve::ExpandMsgXof, G1Projective, Scalar};
6 | use digest::{ExtendableOutput, Update, XofReader};
7 | use sha3::Shake256;
8 |
9 | const TO_SCALAR_DST: &[u8] = b"OBERON_BLS12381FQ_XOF:SHAKE-256_";
10 | const TO_CURVE_DST: &[u8] = b"OBERON_BLS12381G1_XOF:SHAKE-256_SSWU_RO_";
11 |
12 | pub fn hash_to_scalar(data: &[&[u8]]) -> Scalar {
13 | let mut hasher = Shake256::default();
14 | hasher.update(TO_SCALAR_DST);
15 | for slice in data {
16 | hasher.update(slice);
17 | }
18 | let mut reader = hasher.finalize_xof();
19 | let mut data = [0u8; 48];
20 | reader.read(&mut data);
21 | Scalar::from_okm(&data)
22 | }
23 |
24 | pub fn hash_to_scalars(data: &[&[u8]], out: &mut [Scalar]) {
25 | let mut hasher = Shake256::default();
26 | hasher.update(TO_SCALAR_DST);
27 | for slice in data {
28 | hasher.update(slice);
29 | }
30 | let mut reader = hasher.finalize_xof();
31 | let mut data = [0u8; 48];
32 | for s in out {
33 | reader.read(&mut data);
34 | *s = Scalar::from_okm(&data);
35 | }
36 | }
37 |
38 | pub fn hash_to_curve(data: &[u8]) -> G1Projective {
39 | G1Projective::hash::>(data, TO_CURVE_DST)
40 | }
41 |
--------------------------------------------------------------------------------
/rust/src/web.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | use crate::{Blinding, Proof, PublicKey, SecretKey, Token};
6 | use rand::prelude::*;
7 | use wasm_bindgen::prelude::*;
8 |
9 | use std::vec::Vec;
10 |
11 | type BlindingList = Vec;
12 |
13 | /// Create new random secret key
14 | #[wasm_bindgen]
15 | pub fn new_secret_key() -> SecretKey {
16 | let rng = thread_rng();
17 | SecretKey::new(rng)
18 | }
19 |
20 | /// Get the public key from the secret key
21 | #[wasm_bindgen]
22 | pub fn get_public_key(sk: SecretKey) -> PublicKey {
23 | PublicKey::from(&sk)
24 | }
25 |
26 | /// Create new secret key from a seed
27 | #[wasm_bindgen]
28 | pub fn secret_key_from_seed(seed: &[u8]) -> SecretKey {
29 | SecretKey::hash(seed)
30 | }
31 |
32 | /// Create a new token for a given ID
33 | #[wasm_bindgen]
34 | pub fn new_token(sk: SecretKey, id: &[u8]) -> Option {
35 | Token::new(&sk, id)
36 | }
37 |
38 | /// Verify a token for a given ID
39 | #[wasm_bindgen]
40 | pub fn verify_token(token: Token, pk: PublicKey, id: &[u8]) -> bool {
41 | token.verify(pk, id).unwrap_u8() == 1
42 | }
43 |
44 | /// Create a blinding factor from the specified data
45 | #[wasm_bindgen]
46 | pub fn create_blinding(data: &[u8]) -> Blinding {
47 | Blinding::new(data)
48 | }
49 |
50 | /// Adds a blinding factor to the token
51 | #[wasm_bindgen]
52 | pub fn add_blinding(token: Token, data: &[u8]) -> Token {
53 | token - Blinding::new(data)
54 | }
55 |
56 | /// Removes a blinding factor to the token
57 | #[wasm_bindgen]
58 | pub fn remove_blinding(token: Token, data: &[u8]) -> Token {
59 | token + Blinding::new(data)
60 | }
61 |
62 | /// Creates a proof using a nonce received from a verifier
63 | #[wasm_bindgen]
64 | pub fn create_proof(token: Token, id: &[u8], blindings: JsValue, nonce: &[u8]) -> Option {
65 | let rng = thread_rng();
66 | match serde_json::from_str::(&blindings.as_string().unwrap()) {
67 | Err(_) => None,
68 | Ok(bs) => Proof::new(&token, &bs, id, nonce, rng),
69 | }
70 | }
71 |
72 | /// Creates a proof using a nonce received from a verifier
73 | #[wasm_bindgen]
74 | pub fn verify_proof(proof: Proof, pk: PublicKey, id: &[u8], nonce: &[u8]) -> bool {
75 | proof.open(pk, id, nonce).unwrap_u8() == 1
76 | }
77 |
--------------------------------------------------------------------------------
/rust/test.php:
--------------------------------------------------------------------------------
1 | Self {
10 | Self(rand_xorshift::XorShiftRng::from_seed(seed))
11 | }
12 | }
13 |
14 | impl rand_core::CryptoRng for MockRng {}
15 |
16 | impl rand_core::RngCore for MockRng {
17 | fn next_u32(&mut self) -> u32 {
18 | self.0.next_u32()
19 | }
20 |
21 | fn next_u64(&mut self) -> u64 {
22 | self.0.next_u64()
23 | }
24 |
25 | fn fill_bytes(&mut self, dest: &mut [u8]) {
26 | self.0.fill_bytes(dest)
27 | }
28 |
29 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
30 | self.0.try_fill_bytes(dest)
31 | }
32 | }
33 |
34 | impl MockRng {
35 | pub fn new() -> Self {
36 | use rand_core::SeedableRng;
37 | Self(rand_xorshift::XorShiftRng::from_seed(SEED))
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/rust/tests/proof.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | mod common;
6 |
7 | use common::{MockRng, ID};
8 | use oberon::{Blinding, Proof, PublicKey, SecretKey};
9 | use rand_core::RngCore;
10 |
11 | #[test]
12 | fn proof_works() {
13 | let mut rng = MockRng::new();
14 | let sk = SecretKey::new(&mut rng);
15 | let pk = PublicKey::from(&sk);
16 | let token = sk.sign(ID).unwrap();
17 | let blinding = Blinding::new(b"1234");
18 | let blinded_token = token - &blinding;
19 |
20 | // sent from verifier, could also be a timestamp in milliseconds as unsigned 8 byte integer
21 | let mut nonce = [0u8; 16];
22 | rng.fill_bytes(&mut nonce);
23 |
24 | let opt_proof = Proof::new(&blinded_token, &[blinding], ID, &nonce, &mut rng);
25 | assert!(opt_proof.is_some());
26 | let proof = opt_proof.unwrap();
27 |
28 | // Send proof, id, nonce to verifier
29 | assert_eq!(proof.open(pk, ID, nonce).unwrap_u8(), 1u8);
30 | assert_eq!(proof.open(pk, b"wrong id", nonce).unwrap_u8(), 0u8);
31 | assert_eq!(proof.open(pk, ID, b"wrong nonce").unwrap_u8(), 0u8);
32 |
33 | // check proof serde
34 | let proof_bytes = proof.to_bytes();
35 | let opt_proof = Proof::from_bytes(&proof_bytes);
36 | assert_eq!(opt_proof.is_some().unwrap_u8(), 1u8);
37 | let proof = opt_proof.unwrap();
38 | assert_eq!(proof.open(pk, ID, nonce).unwrap_u8(), 1u8);
39 |
40 | // No blinding factor
41 | let opt_proof = Proof::new(&blinded_token, &[], ID, &nonce, &mut rng);
42 | assert!(opt_proof.is_some());
43 | let proof = opt_proof.unwrap();
44 |
45 | // Send proof, id, nonce to verifier
46 | assert_eq!(proof.open(pk, ID, nonce).unwrap_u8(), 0u8);
47 |
48 | // proof to bytes
49 | println!("{:?}", proof.to_bytes());
50 | assert_eq!(
51 | proof.to_bytes(),
52 | [
53 | 171, 50, 166, 43, 168, 199, 83, 254, 187, 82, 10, 20, 80, 106, 217, 99, 152, 85, 146,
54 | 201, 116, 160, 65, 177, 74, 89, 56, 163, 249, 54, 78, 230, 45, 98, 181, 248, 14, 40,
55 | 206, 168, 136, 107, 154, 224, 116, 86, 210, 236, 160, 236, 124, 238, 208, 209, 161, 12,
56 | 122, 2, 17, 55, 18, 187, 110, 106, 177, 222, 130, 252, 100, 226, 32, 255, 118, 230,
57 | 179, 233, 49, 240, 51, 248, 199, 92, 53, 218, 200, 163, 69, 13, 111, 255, 189, 151, 65,
58 | 122, 150, 192
59 | ]
60 | )
61 | }
62 |
63 | #[test]
64 | fn serialization_test() {
65 | let ct_proof = Proof::from_bytes(&[
66 | 171, 50, 166, 43, 168, 199, 83, 254, 187, 82, 10, 20, 80, 106, 217, 99, 152, 85, 146, 201,
67 | 116, 160, 65, 177, 74, 89, 56, 163, 249, 54, 78, 230, 45, 98, 181, 248, 14, 40, 206, 168,
68 | 136, 107, 154, 224, 116, 86, 210, 236, 160, 236, 124, 238, 208, 209, 161, 12, 122, 2, 17,
69 | 55, 18, 187, 110, 106, 177, 222, 130, 252, 100, 226, 32, 255, 118, 230, 179, 233, 49, 240,
70 | 51, 248, 199, 92, 53, 218, 200, 163, 69, 13, 111, 255, 189, 151, 65, 122, 150, 192,
71 | ]);
72 | assert_eq!(ct_proof.is_some().unwrap_u8(), 1u8);
73 | let proof = ct_proof.unwrap();
74 | let s = serde_json::to_string(&proof).unwrap();
75 | println!("len = {}", s.len());
76 | println!("json = {:#?}", s);
77 | println!(
78 | "cbor = {}",
79 | hex::encode(&serde_bare::to_vec(&proof).unwrap())
80 | );
81 | }
82 |
83 | #[test]
84 | fn vectors() {
85 | let mut rng = MockRng::new();
86 | let sk = SecretKey::new(&mut rng);
87 | let pk = PublicKey::from(&sk);
88 | let id = hex::decode("aa").unwrap();
89 | let token = sk.sign(&id).unwrap();
90 | let mut nonce = [0u8; 16];
91 | rng.fill_bytes(&mut nonce);
92 | let proof = Proof::new(&token, &[], &id, nonce, &mut rng).unwrap();
93 | println!("sk = {}", hex::encode(sk.to_bytes()));
94 | println!("token = {}", hex::encode(token.to_bytes()));
95 | println!("nonce = {}", hex::encode(nonce));
96 | println!("proof = {}", hex::encode(proof.to_bytes()));
97 | println!("open = {}", proof.open(pk, &id, nonce).unwrap_u8())
98 | }
99 |
--------------------------------------------------------------------------------
/rust/tests/public_key.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | mod common;
6 |
7 | use common::{MockRng, SEED};
8 | use oberon::{PublicKey, SecretKey};
9 |
10 | #[test]
11 | fn new_random_public_key() {
12 | let rng = MockRng::new();
13 | let sk = SecretKey::new(rng);
14 | let pk = PublicKey::from(&sk);
15 | assert_eq!(
16 | pk.to_bytes(),
17 | [
18 | 179, 2, 6, 45, 239, 85, 45, 114, 170, 112, 126, 208, 90, 215, 179, 26, 11, 176, 144,
19 | 41, 15, 193, 255, 233, 143, 170, 15, 56, 238, 202, 106, 80, 78, 237, 239, 171, 83, 49,
20 | 110, 245, 97, 185, 166, 210, 249, 175, 138, 99, 16, 210, 181, 179, 160, 67, 31, 64,
21 | 150, 118, 94, 150, 255, 219, 120, 116, 8, 82, 115, 169, 172, 209, 210, 0, 251, 2, 144,
22 | 67, 22, 195, 77, 233, 65, 94, 181, 131, 178, 50, 103, 60, 153, 177, 70, 226, 211, 181,
23 | 202, 184, 130, 142, 133, 245, 219, 22, 200, 148, 241, 229, 69, 91, 93, 229, 18, 242,
24 | 13, 174, 216, 11, 153, 103, 255, 84, 237, 67, 225, 42, 161, 83, 133, 52, 72, 99, 90,
25 | 158, 169, 19, 170, 61, 105, 217, 226, 199, 144, 119, 138, 87, 16, 187, 136, 160, 199,
26 | 47, 217, 238, 234, 30, 95, 28, 110, 4, 133, 199, 34, 17, 120, 45, 97, 27, 254, 223,
27 | 253, 68, 53, 122, 100, 226, 236, 202, 201, 61, 95, 58, 14, 117, 165, 94, 69, 140, 85,
28 | 85, 92, 188, 141, 49, 139, 205, 201, 238, 245, 185, 200, 112, 157, 191, 241, 64, 100,
29 | 189, 36, 57, 146, 181, 230, 211, 95, 147, 113, 195, 197, 189, 104, 208, 155, 73, 108,
30 | 25, 215, 253, 192, 73, 94, 148, 89, 60, 223, 105, 194, 160, 18, 59, 186, 19, 22, 143,
31 | 40, 178, 135, 124, 20, 215, 44, 183, 89, 5, 138, 69, 186, 249, 203, 119, 101, 39, 79,
32 | 237, 182, 160, 160, 151, 213, 205, 211, 130, 186, 223, 59, 83, 178, 132, 53, 174, 246,
33 | 49, 20, 79, 165, 202, 217, 129, 192, 197
34 | ]
35 | );
36 | }
37 |
38 | #[test]
39 | fn new_seeded_public_key() {
40 | let sk = SecretKey::hash(&SEED[..]);
41 | let pk = PublicKey::from(&sk);
42 | assert_eq!(
43 | pk.to_bytes(),
44 | [
45 | 180, 38, 11, 239, 38, 13, 55, 164, 241, 71, 116, 212, 199, 63, 84, 180, 147, 139, 93,
46 | 230, 92, 253, 51, 91, 148, 102, 103, 112, 252, 187, 0, 198, 76, 156, 221, 125, 188, 60,
47 | 106, 240, 171, 134, 4, 43, 181, 132, 243, 86, 19, 131, 35, 193, 11, 141, 217, 90, 126,
48 | 18, 252, 181, 92, 113, 11, 133, 62, 134, 83, 59, 40, 90, 149, 137, 10, 244, 142, 246,
49 | 231, 189, 190, 13, 242, 219, 53, 77, 95, 185, 35, 153, 49, 49, 140, 120, 174, 222, 157,
50 | 204, 173, 217, 115, 44, 227, 18, 211, 224, 46, 114, 159, 112, 116, 208, 215, 89, 216,
51 | 4, 120, 89, 40, 22, 110, 223, 254, 76, 237, 99, 144, 174, 177, 112, 234, 65, 145, 185,
52 | 115, 155, 251, 227, 247, 132, 185, 67, 70, 79, 238, 175, 10, 46, 110, 78, 81, 78, 191,
53 | 226, 93, 55, 207, 2, 199, 19, 108, 92, 197, 106, 249, 32, 234, 225, 25, 79, 255, 152,
54 | 168, 12, 177, 99, 29, 186, 114, 248, 13, 232, 144, 247, 83, 67, 103, 230, 33, 16, 67,
55 | 13, 111, 130, 173, 139, 13, 72, 230, 231, 203, 86, 184, 6, 54, 27, 75, 145, 88, 229,
56 | 167, 217, 165, 203, 238, 167, 47, 56, 58, 111, 49, 165, 235, 232, 170, 186, 73, 201,
57 | 106, 59, 33, 120, 164, 229, 196, 194, 124, 165, 226, 197, 137, 73, 18, 93, 20, 166,
58 | 218, 166, 173, 245, 241, 251, 229, 254, 245, 7, 24, 81, 180, 10, 141, 115, 9, 149, 120,
59 | 120, 142, 4, 173, 215, 155, 222, 118, 153, 38, 149, 45, 148, 28, 120, 60, 198, 252, 61,
60 | 103, 242, 3, 18, 195, 246
61 | ]
62 | )
63 | }
64 |
65 | #[test]
66 | fn public_key_from_bytes() {
67 | let esk = SecretKey::hash(&SEED[..]);
68 | let epk = PublicKey::from(&esk);
69 | let apk = PublicKey::from_bytes(&[
70 | 180, 38, 11, 239, 38, 13, 55, 164, 241, 71, 116, 212, 199, 63, 84, 180, 147, 139, 93, 230,
71 | 92, 253, 51, 91, 148, 102, 103, 112, 252, 187, 0, 198, 76, 156, 221, 125, 188, 60, 106,
72 | 240, 171, 134, 4, 43, 181, 132, 243, 86, 19, 131, 35, 193, 11, 141, 217, 90, 126, 18, 252,
73 | 181, 92, 113, 11, 133, 62, 134, 83, 59, 40, 90, 149, 137, 10, 244, 142, 246, 231, 189, 190,
74 | 13, 242, 219, 53, 77, 95, 185, 35, 153, 49, 49, 140, 120, 174, 222, 157, 204, 173, 217,
75 | 115, 44, 227, 18, 211, 224, 46, 114, 159, 112, 116, 208, 215, 89, 216, 4, 120, 89, 40, 22,
76 | 110, 223, 254, 76, 237, 99, 144, 174, 177, 112, 234, 65, 145, 185, 115, 155, 251, 227, 247,
77 | 132, 185, 67, 70, 79, 238, 175, 10, 46, 110, 78, 81, 78, 191, 226, 93, 55, 207, 2, 199, 19,
78 | 108, 92, 197, 106, 249, 32, 234, 225, 25, 79, 255, 152, 168, 12, 177, 99, 29, 186, 114,
79 | 248, 13, 232, 144, 247, 83, 67, 103, 230, 33, 16, 67, 13, 111, 130, 173, 139, 13, 72, 230,
80 | 231, 203, 86, 184, 6, 54, 27, 75, 145, 88, 229, 167, 217, 165, 203, 238, 167, 47, 56, 58,
81 | 111, 49, 165, 235, 232, 170, 186, 73, 201, 106, 59, 33, 120, 164, 229, 196, 194, 124, 165,
82 | 226, 197, 137, 73, 18, 93, 20, 166, 218, 166, 173, 245, 241, 251, 229, 254, 245, 7, 24, 81,
83 | 180, 10, 141, 115, 9, 149, 120, 120, 142, 4, 173, 215, 155, 222, 118, 153, 38, 149, 45,
84 | 148, 28, 120, 60, 198, 252, 61, 103, 242, 3, 18, 195, 246,
85 | ]);
86 | assert_eq!(apk.is_some().unwrap_u8(), 1);
87 | assert_eq!(epk, apk.unwrap());
88 | }
89 |
--------------------------------------------------------------------------------
/rust/tests/secret_key.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | mod common;
6 |
7 | use common::{MockRng, SEED};
8 | use oberon::SecretKey;
9 |
10 | #[test]
11 | fn new_random_secret_key() {
12 | let rng = MockRng::new();
13 | let sk = SecretKey::new(rng);
14 | assert_eq!(
15 | sk.to_bytes(),
16 | [
17 | 180, 92, 239, 44, 240, 143, 149, 163, 45, 177, 22, 179, 146, 120, 129, 229, 78, 56, 70,
18 | 205, 251, 160, 140, 79, 159, 138, 6, 56, 250, 236, 176, 11, 70, 53, 138, 199, 245, 180,
19 | 223, 213, 128, 166, 122, 225, 67, 58, 138, 201, 19, 114, 57, 149, 70, 141, 31, 45, 180,
20 | 30, 208, 222, 234, 112, 21, 34, 37, 5, 163, 172, 96, 40, 81, 27, 89, 86, 163, 93, 15,
21 | 201, 200, 183, 157, 18, 134, 140, 156, 43, 79, 231, 42, 234, 198, 139, 130, 52, 176,
22 | 106
23 | ]
24 | );
25 | }
26 |
27 | #[test]
28 | fn new_seeded_secret_key() {
29 | let sk = SecretKey::hash(&SEED[..]);
30 | assert_eq!(
31 | sk.to_bytes(),
32 | [
33 | 16, 133, 126, 11, 192, 153, 22, 14, 53, 214, 99, 40, 66, 194, 96, 30, 19, 86, 137, 107,
34 | 150, 49, 104, 202, 209, 80, 128, 182, 15, 154, 34, 57, 100, 51, 175, 108, 12, 56, 6,
35 | 76, 46, 173, 247, 255, 184, 165, 228, 127, 145, 65, 171, 195, 44, 164, 3, 16, 132, 43,
36 | 108, 82, 63, 136, 116, 3, 93, 1, 226, 152, 197, 152, 61, 212, 185, 32, 195, 211, 37,
37 | 206, 242, 31, 72, 79, 83, 71, 197, 102, 202, 129, 95, 19, 105, 34, 22, 46, 124, 94
38 | ]
39 | )
40 | }
41 |
42 | #[test]
43 | fn secret_key_from_bytes() {
44 | let esk = SecretKey::hash(&SEED[..]);
45 | let ask = SecretKey::from_bytes(&[
46 | 16, 133, 126, 11, 192, 153, 22, 14, 53, 214, 99, 40, 66, 194, 96, 30, 19, 86, 137, 107,
47 | 150, 49, 104, 202, 209, 80, 128, 182, 15, 154, 34, 57, 100, 51, 175, 108, 12, 56, 6, 76,
48 | 46, 173, 247, 255, 184, 165, 228, 127, 145, 65, 171, 195, 44, 164, 3, 16, 132, 43, 108, 82,
49 | 63, 136, 116, 3, 93, 1, 226, 152, 197, 152, 61, 212, 185, 32, 195, 211, 37, 206, 242, 31,
50 | 72, 79, 83, 71, 197, 102, 202, 129, 95, 19, 105, 34, 22, 46, 124, 94,
51 | ]);
52 | assert_eq!(ask.is_some().unwrap_u8(), 1);
53 | assert_eq!(esk, ask.unwrap());
54 | }
55 |
--------------------------------------------------------------------------------
/rust/tests/token.rs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright Michael Lodder. All Rights Reserved.
3 | SPDX-License-Identifier: Apache-2.0
4 | */
5 | mod common;
6 |
7 | use common::{MockRng, ID};
8 | use oberon::{PublicKey, SecretKey, Token};
9 |
10 | #[test]
11 | fn valid_token() {
12 | let sk = SecretKey::new(MockRng::new());
13 | let pk = PublicKey::from(&sk);
14 | let opt_token = sk.sign(ID);
15 |
16 | assert!(opt_token.is_some());
17 | let token = opt_token.unwrap();
18 | assert_eq!(token.verify(pk, ID).unwrap_u8(), 1u8);
19 | assert_eq!(token.verify(pk, b"wrong identity").unwrap_u8(), 0u8);
20 | }
21 |
22 | #[test]
23 | fn token_from_bytes() {
24 | let sk = SecretKey::new(MockRng::new());
25 | let exp_token = sk.sign(ID).unwrap();
26 | let opt_act_token = Token::from_bytes(&[
27 | 174, 221, 77, 7, 147, 66, 236, 180, 112, 106, 14, 104, 35, 123, 13, 189, 211, 158, 32, 194,
28 | 24, 50, 49, 93, 87, 126, 102, 20, 192, 132, 157, 221, 83, 98, 81, 93, 155, 137, 134, 9, 58,
29 | 108, 30, 237, 108, 13, 40, 242,
30 | ]);
31 | assert_eq!(opt_act_token.is_some().unwrap_u8(), 1u8);
32 | let act_token = opt_act_token.unwrap();
33 | assert_eq!(act_token, exp_token);
34 |
35 | println!("{}", hex::encode(&serde_bare::to_vec(&exp_token).unwrap()));
36 | }
37 |
--------------------------------------------------------------------------------
/test_vectors/blinding.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "in": "",
4 | "out": "b56779b433e83079324eb03f2b31b320df167b6f627e417813595ed2b178039fcda4dd5da879825b049b39c0439c8f7f"
5 | },
6 | {
7 | "in": "aaaa",
8 | "out": "a53bb8dabda430bd10d74868c1eb2118a9987b4629593cd3b716cf462bd0b410758c5ba98949c9b7e1bb7d93ce978109"
9 | },
10 | {
11 | "in": "aaaaaaaaaa",
12 | "out": "84e9ebc37b27fabcd833e9b42bd3e632aea6df1de58c54ef0d8a54ba5ef08f50e7e614e77a4fcc303b609520c4349741"
13 | },
14 | {
15 | "in": "1234",
16 | "out": "80a239769c64ce5b3fa70d1ab5feb704ced21a048154def378dc35b7a673016e37ae2d1ff50b5535383b9f6198756910"
17 | }
18 | ]
19 |
--------------------------------------------------------------------------------
/test_vectors/proof.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id":"aa",
4 | "sk":"b45cef2cf08f95a32db116b3927881e54e3846cdfba08c4f9f8a0638faecb00b46358ac7f5b4dfd580a67ae1433a8ac913723995468d1f2db41ed0deea7015222505a3ac6028511b5956a35d0fc9c8b79d12868c9c2b4fe72aeac68b8234b06a",
5 | "nonce":"8aa2035b4c22f09d955e5de4d6333288",
6 | "token":"8f4c47b8b56cffb091579fb2ded6b946c659b27b97b8d1719897efbd752c4a0af08f471afef1676adb774e631e9cdc34",
7 | "proof":"a99748bd7e02802827aea3a4298b721f2d2e8a5d359655deadcb416d4e515ca9cd9291b83f8e4a159aaa132ae57f251a80925021351b7fe3a64b6744324eb1ad1940f9ab6d76ca2055c55fa8d4a9c7448be5049a3c856b930b3a1c5633d7c1ec"
8 | }
9 | ]
10 |
--------------------------------------------------------------------------------
/test_vectors/token.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "sk":"b45cef2cf08f95a32db116b3927881e54e3846cdfba08c4f9f8a0638faecb00b46358ac7f5b4dfd580a67ae1433a8ac913723995468d1f2db41ed0deea7015222505a3ac6028511b5956a35d0fc9c8b79d12868c9c2b4fe72aeac68b8234b06a",
4 | "id":"",
5 | "m": "ed7621c5515e766d0ffc965f85b04d43ee328d8e14ba7164f30b15f3e569a031",
6 | "m'":"1ae77ab2ea9feaf4078d54393f2675823040d158d4f64ed3aed3f1976b6ff74e",
7 | "u":"90abc598b162b34552656d89f8219f2591fbb012c04c5a98644d9569940311f5262b092e8b7bdb2ee5a72dc4cc4f3a04",
8 | "token":"805e2c5dd5e3519f9bd31cdd67cae8e5d8dbf109cd647444b6d5d4d7be9a3bdc1e0f12a0640c413ca2ce451bfdbfaef9"
9 | },
10 | {
11 | "sk":"b45cef2cf08f95a32db116b3927881e54e3846cdfba08c4f9f8a0638faecb00b46358ac7f5b4dfd580a67ae1433a8ac913723995468d1f2db41ed0deea7015222505a3ac6028511b5956a35d0fc9c8b79d12868c9c2b4fe72aeac68b8234b06a",
12 | "id":"aaaa",
13 | "m":"6f5ac83d569b2406a43dbec1c9d3a5f396f43bfb0201a18e91261e08bf150050",
14 | "m'":"56cc0d8f5a8c19db7dc22dadea44381604fce38384a1f3c7f59e903068a9e358",
15 | "u":"af70aadb78c31450d6d14a29547a9d3e4f770109214ff6ee910bfc4edbf59eeb1ff693d8ce55e0d0739e99016ce1758c",
16 | "token":"a3f4b0f7ba7857ec2e8b288fd65796434832e2b263f08f6cd9d0b3aa08f91f938392f8938eb5090ede60066efe061af6"
17 | },
18 | {
19 | "sk":"b45cef2cf08f95a32db116b3927881e54e3846cdfba08c4f9f8a0638faecb00b46358ac7f5b4dfd580a67ae1433a8ac913723995468d1f2db41ed0deea7015222505a3ac6028511b5956a35d0fc9c8b79d12868c9c2b4fe72aeac68b8234b06a",
20 | "id":"aaaaaaaaaa",
21 | "m":"2e31953c528d5a83836f323effed95a42a525e1dd93d254da86fc628def3e025",
22 | "m'":"4b6d40bbfe4e3f30a7d00b16f83b4bf3efcb587c1b0cb0f28ce2ecc5ca85bd0b",
23 | "u":"9542afe2ec1a36a27c6df23565328486c01629e0343d6fe9c9001d0643a784d77298d46c88a5073b5866357de75c8f5e",
24 | "token":"8a4ebfed0bbf26a21bfd950d9118297c5b8de9b16486269920a2b6abd3083da16e9573306eb725ef5cabe8028aa952b8"
25 | }
26 | ]
27 |
--------------------------------------------------------------------------------