From 2aa295009858d1c2f63f846d5f7eb45803b08964 Mon Sep 17 00:00:00 2001
From: librelois <elois@ifee.fr>
Date: Thu, 18 Jun 2020 03:07:43 +0200
Subject: [PATCH] [feat] oxyde scrypt

---
 app/modules/keypair/index.ts                  | 13 ++++----
 app/modules/keypair/lib/scrypt.ts             | 31 +++++--------------
 neon/lib/index.ts                             |  2 +-
 neon/native/crypto.d.ts                       |  1 +
 neon/native/index.d.ts                        |  1 +
 neon/native/src/crypto.rs                     | 28 ++++++++++-------
 neon/native/src/lib.rs                        | 12 +++++++
 .../extra/completion/duniter_completion.bash  |  6 ++--
 .../modules/keypair/keypair-module-test.ts    |  4 +--
 test/neon/test_crypto.ts                      | 14 ++++++++-
 10 files changed, 64 insertions(+), 48 deletions(-)

diff --git a/app/modules/keypair/index.ts b/app/modules/keypair/index.ts
index a809f75fd..d69751b51 100644
--- a/app/modules/keypair/index.ts
+++ b/app/modules/keypair/index.ts
@@ -33,19 +33,18 @@ export const KeypairDependency = {
         desc: "Password to generate the keypair",
       },
       {
-        value: "--keyN <N>",
-        desc:
-          "Scrypt `N` CPU/memory cost parameter. Must be a power of 2. Defaults to 4096.",
+        value: "--scrypt-log-n <log_n>",
+        desc: "Scrypt `log(n)` CPU/memory cost parameter. Defaults to 12.",
         parser: parseInt,
       },
       {
-        value: "--keyr <r>",
+        value: "--scrypt-r <r>",
         desc:
           "Scrypt `r` The blocksize parameter, which fine-tunes sequential memory read size and performance. Defaults to 16.",
         parser: parseInt,
       },
       {
-        value: "--keyp <p>",
+        value: "--scrypt-p <p>",
         desc: "Scrypt `p` Parallelization parameter. Defaults to 1.",
         parser: parseInt,
       },
@@ -99,11 +98,11 @@ export const KeypairDependency = {
         confDAL: any
       ) => {
         if (
-          (program.keyN || program.keyr || program.keyp) &&
+          (program.scryptLogN || program.scryptR || program.scryptP) &&
           !(program.salt && program.passwd)
         ) {
           throw Error(
-            "Missing --salt and --passwd options along with --keyN|keyr|keyp option"
+            "Missing --salt and --passwd options along with --scrypt-log-n|scrypt-r|scrypt-p option"
           );
         }
 
diff --git a/app/modules/keypair/lib/scrypt.ts b/app/modules/keypair/lib/scrypt.ts
index 3135ecad6..1c946dc61 100644
--- a/app/modules/keypair/lib/scrypt.ts
+++ b/app/modules/keypair/lib/scrypt.ts
@@ -11,8 +11,7 @@
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 // GNU Affero General Public License for more details.
 
-import * as crypto from "crypto";
-import { KeyPairBuilder, seedToSecretKey } from "../../../../neon/lib";
+import { KeyPairBuilder, scrypt, seedToSecretKey } from "../../../../neon/lib";
 
 const SEED_LENGTH = 32; // Length of the key
 
@@ -20,7 +19,7 @@ const SEED_LENGTH = 32; // Length of the key
  * Generates a new keypair object from salt + password strings.
  * @param salt
  * @param key
- * @param N Scrypt parameter N. Defaults to 4096.
+ * @param log_n Scrypt parameter log n. Defaults to 12.
  * @param r Scrypt parameter r. Defaults to 16.
  * @param p Scrypt parameter p. Defaults to 1.
  * @return keyPair An object containing the public and private keys, base58 encoded.
@@ -28,28 +27,14 @@ const SEED_LENGTH = 32; // Length of the key
 export const Scrypt = async (
   salt: string,
   key: string,
-  N = 4096,
+  log_n = 12,
   r = 16,
   p = 1
 ) => {
-  const res: { pub: string; sec: string } = await new Promise(
-    (resolve, reject) => {
-      crypto.scrypt(
-        key,
-        salt,
-        SEED_LENGTH,
-        { N, r, p },
-        (err: any, seed: Buffer) => {
-          if (err) return reject(err);
-          const pair = KeyPairBuilder.fromSeed(seed);
-          resolve({
-            pub: pair.getPublicKey(),
-            sec: seedToSecretKey(seed),
-          });
-        }
-      );
-    }
-  );
+  const seed = scrypt(salt, key, log_n, r, p);
 
-  return res;
+  return {
+    pub: KeyPairBuilder.fromSeed(seed).getPublicKey(),
+    sec: seedToSecretKey(seed),
+  };
 };
diff --git a/neon/lib/index.ts b/neon/lib/index.ts
index 48fdad95c..18314003a 100644
--- a/neon/lib/index.ts
+++ b/neon/lib/index.ts
@@ -1,3 +1,3 @@
-export { Ed25519Signator, generateRandomSeed, seedToSecretKey, sha256, verify, Wot } from "../native";
+export { Ed25519Signator, generateRandomSeed, seedToSecretKey, scrypt, sha256, verify, Wot } from "../native";
 export { KeyPairBuilder } from "./crypto";
 export { WotBuilder } from "./wot";
diff --git a/neon/native/crypto.d.ts b/neon/native/crypto.d.ts
index ba6c22595..ea2710c89 100644
--- a/neon/native/crypto.d.ts
+++ b/neon/native/crypto.d.ts
@@ -11,5 +11,6 @@ export class Ed25519Signator {
 
 export function generateRandomSeed(): Buffer;
 export function seedToSecretKey(seed: Buffer): string;
+export function scrypt(salt: string, password: string, log_n: number, r: number, p: number): Buffer;
 export function sha256(data: string): string;
 export function verify(message: Buffer | string, sig: string, pubkey: string): boolean;
diff --git a/neon/native/index.d.ts b/neon/native/index.d.ts
index e31c8cbdf..a1029c937 100644
--- a/neon/native/index.d.ts
+++ b/neon/native/index.d.ts
@@ -6,6 +6,7 @@ import * as _wot from './wot';
 export import Ed25519Signator = _crypto.Ed25519Signator;
 export import generateRandomSeed = _crypto.generateRandomSeed;
 export import seedToSecretKey = _crypto.seedToSecretKey;
+export import scrypt = _crypto.scrypt;
 export import sha256 = _crypto.sha256;
 export import verify = _crypto.verify;
 
diff --git a/neon/native/src/crypto.rs b/neon/native/src/crypto.rs
index 76414e8df..68573d2f7 100644
--- a/neon/native/src/crypto.rs
+++ b/neon/native/src/crypto.rs
@@ -13,13 +13,13 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-use crate::into_neon_res;
+use crate::{create_js_buffer, into_neon_res};
 use dup_crypto::bases::b58::ToBase58;
 use dup_crypto::hashs::Hash;
 use dup_crypto::keys::{
     ed25519::{
-        Ed25519KeyPair, KeyPairFromSeed32Generator, PublicKey as Ed25519PublicKey,
-        Signator as Ed25519Signator, Signature as Ed25519Signature,
+        Ed25519KeyPair, KeyPairFromSaltedPasswordGenerator, KeyPairFromSeed32Generator,
+        PublicKey as Ed25519PublicKey, Signator as Ed25519Signator, Signature as Ed25519Signature,
     },
     KeyPair, PublicKey, Signator, Signature,
 };
@@ -34,14 +34,7 @@ pub fn generate_random_seed(mut cx: FunctionContext) -> JsResult<JsBuffer> {
         Seed32::random().map_err(|_| "fail to generate random seed"),
     )?;
 
-    let mut js_buffer = JsBuffer::new(&mut cx, 32)?;
-
-    cx.borrow_mut(&mut js_buffer, |data| {
-        let slice = data.as_mut_slice::<u8>();
-        slice.copy_from_slice(seed.as_ref());
-    });
-
-    Ok(js_buffer)
+    create_js_buffer(&mut cx, seed.as_ref())
 }
 
 pub fn seed_to_expanded_base58_secret_key(mut cx: FunctionContext) -> JsResult<JsString> {
@@ -62,6 +55,19 @@ pub fn seed_to_expanded_base58_secret_key(mut cx: FunctionContext) -> JsResult<J
     Ok(cx.string(expanded_base58_secret_key))
 }
 
+pub fn scrypt(mut cx: FunctionContext) -> JsResult<JsBuffer> {
+    let salt = cx.argument::<JsString>(0)?.value();
+    let password = cx.argument::<JsString>(1)?.value();
+    let log_n = cx.argument::<JsNumber>(2)?.value() as u8;
+    let r = cx.argument::<JsNumber>(3)?.value() as u32;
+    let p = cx.argument::<JsNumber>(4)?.value() as u32;
+
+    let seed = KeyPairFromSaltedPasswordGenerator::with_parameters(log_n, r, p)
+        .generate_seed(password.as_bytes(), salt.as_bytes());
+
+    create_js_buffer(&mut cx, seed.as_ref())
+}
+
 pub fn sha256(mut cx: FunctionContext) -> JsResult<JsString> {
     let str_datas = cx.argument::<JsString>(0)?.value();
     Ok(cx.string(Hash::compute_str(&str_datas).to_hex().to_uppercase()))
diff --git a/neon/native/src/lib.rs b/neon/native/src/lib.rs
index 94aa1964d..89da67cb5 100644
--- a/neon/native/src/lib.rs
+++ b/neon/native/src/lib.rs
@@ -18,6 +18,17 @@ mod wot;
 
 use neon::{prelude::*, register_module};
 
+fn create_js_buffer<'c, C: Context<'c>>(context: &mut C, bytes: &[u8]) -> JsResult<'c, JsBuffer> {
+    let mut js_buffer = unsafe { JsBuffer::uninitialized(context, bytes.len() as u32)? };
+
+    context.borrow_mut(&mut js_buffer, |data| {
+        let slice = data.as_mut_slice::<u8>();
+        slice.copy_from_slice(bytes);
+    });
+
+    Ok(js_buffer)
+}
+
 fn into_neon_res<'c, C: Context<'c>, T, S: AsRef<str>>(
     context: &mut C,
     rust_result: Result<T, S>,
@@ -34,6 +45,7 @@ register_module!(mut cx, {
         "seedToSecretKey",
         crate::crypto::seed_to_expanded_base58_secret_key,
     )?;
+    cx.export_function("scrypt", crate::crypto::scrypt)?;
     cx.export_function("sha256", crate::crypto::sha256)?;
     cx.export_function("verify", crate::crypto::verify)?;
     cx.export_class::<crate::crypto::JsKeyPair>("Ed25519Signator")?;
diff --git a/release/extra/completion/duniter_completion.bash b/release/extra/completion/duniter_completion.bash
index 9333250df..941b66532 100644
--- a/release/extra/completion/duniter_completion.bash
+++ b/release/extra/completion/duniter_completion.bash
@@ -87,9 +87,9 @@ direct_webstart \
 --at \
 --salt \
 --passwd \
---keyN \
---keyr \
---keyp \
+--scrypt-log-n \
+--scrypt-r \
+--scrypt-p \
 --keyprompt \
 --keyfile \
 --nointeractive \
diff --git a/test/fast/modules/keypair/keypair-module-test.ts b/test/fast/modules/keypair/keypair-module-test.ts
index 45998ad1e..5a2a3c46d 100644
--- a/test/fast/modules/keypair/keypair-module-test.ts
+++ b/test/fast/modules/keypair/keypair-module-test.ts
@@ -23,12 +23,12 @@ describe('Module usage', () => {
     try {
       const stack = Statics.minimalStack();
       stack.registerDependency(KeypairDependency, 'duniter-keypair');
-      await stack.executeStack(['node', 'index.js', 'config', '--memory', '--keyN', '2048']);
+      await stack.executeStack(['node', 'index.js', 'config', '--memory', '--scrypt-log-n', '2048']);
     } catch (e) {
       errMessage = e.message;
     }
     should.exist(errMessage);
-    should.equal(errMessage, 'Missing --salt and --passwd options along with --keyN|keyr|keyp option');
+    should.equal(errMessage, 'Missing --salt and --passwd options along with --scrypt-log-n|scrypt-r|scrypt-p option');
   })
 
   it('no options on brand new node should generate random key', async () => {
diff --git a/test/neon/test_crypto.ts b/test/neon/test_crypto.ts
index 5a6ac3bd3..c001b9c5b 100644
--- a/test/neon/test_crypto.ts
+++ b/test/neon/test_crypto.ts
@@ -1,6 +1,6 @@
 "use strict";
 
-import { Ed25519Signator, KeyPairBuilder, sha256, verify, generateRandomSeed, seedToSecretKey } from "../../neon/lib";
+import { Ed25519Signator, KeyPairBuilder, scrypt, sha256, verify, generateRandomSeed, seedToSecretKey } from "../../neon/lib";
 import * as assert from "assert";
 
 
@@ -17,6 +17,18 @@ describe('ed25519 tests:', function(){
     //assert.equal(rawSec, '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP');
   })
 
+  it('scrypt 7iMV3b6j2hSj5WtrfchfvxivS9swN3opDgxudeHq64fb', function(done){
+    const seed = scrypt(
+      "JhxtHB7UcsDbA9wMSyMKXUzBZUQvqVyB32KwzS9SWoLkjrUhHV",
+      "JhxtHB7UcsDbA9wMSyMKXUzBZUQvqVyB32KwzS9SWoLkjrUhHV_",
+      12,
+      16,
+      1);
+    let pub = KeyPairBuilder.fromSeed(seed).getPublicKey();
+    assert.equal(pub, "7iMV3b6j2hSj5WtrfchfvxivS9swN3opDgxudeHq64fb")
+    done();
+  });
+
   it('sha256 hello', function(done){
     const msg = "hello";
     const hash = sha256(msg);
-- 
GitLab