├── .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 | ![Three pass](img/three-pass.png) 121 | 122 | 123 | One pass model 124 | 125 | ![One pass](img/one-pass.png) 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 | --------------------------------------------------------------------------------