├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── benchmarks ├── browser.js ├── index.html └── package.json ├── binding.gyp ├── bindings.js ├── index.js ├── js.js ├── karma.conf.js ├── lib ├── index.js └── scrypt.js ├── package.json ├── src ├── addon.cc ├── config-mock.h └── win │ ├── sha256.c │ ├── sys │ └── mman.h │ └── unistd.h └── test ├── index.js └── vectors.json /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | 4 | benchmarks/browser.bundle.js 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "scrypt"] 2 | path = src/scrypt 3 | url = https://github.com/Tarsnap/scrypt 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | os: 3 | - linux 4 | - osx 5 | language: node_js 6 | node_js: 7 | - "4" 8 | - "5" 9 | - "6" 10 | - "7" 11 | - "8" 12 | - "9" 13 | addons: 14 | apt: 15 | sources: 16 | - ubuntu-toolchain-r-test 17 | packages: 18 | - g++-6 19 | env: 20 | global: 21 | - JOBS=2 22 | matrix: 23 | - TEST_SUITE=unit 24 | matrix: 25 | fast_finish: true 26 | include: 27 | - os: linux 28 | node_js: "6" 29 | env: TEST_SUITE=lint 30 | before_install: 31 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-6; fi 32 | script: npm run $TEST_SUITE 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 cryptocoinjs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scrypt 2 | 3 | Version | Mac/Linux | Windows 4 | ------- | --------- | ------- 5 | [![NPM Package](https://img.shields.io/npm/v/scrypt.svg?style=flat-square)](https://www.npmjs.org/package/scrypt) | [![Build Status](https://img.shields.io/travis/cryptocoinjs/scrypt.svg?branch=master&style=flat-square)](https://travis-ci.org/cryptocoinjs/scrypt) | [![AppVeyor](https://img.shields.io/appveyor/ci/fanatid/scrypt.svg?branch=master&style=flat-square)](https://ci.appveyor.com/project/fanatid/scrypt) 6 | 7 | [![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) 8 | 9 | This module provides native bindings to [Colin Percival's scrypt][1]. 10 | 11 | In browser Pure JS implementation (based on [scryptsy](https://github.com/cryptocoinjs/scryptsy)) will be used. 12 | 13 | Current scrypt version: 1.2.1 14 | 15 | This library is experimental, so use at your own risk. 16 | 17 | ## Installation 18 | 19 | `npm install scrypt` 20 | 21 | ##### Windows 22 | 23 | Before install scrypt you should install [windows-build-tools][2]. 24 | 25 | ## API 26 | 27 | Only one function — scrypt. 28 | 29 | `scrypt(Buffer password, Buffer salt, Number N, Number r, Number p, Number length)` 30 | 31 | - `password` - key which will be hashed 32 | - `salt` - salt 33 | - `N` - number of iterations 34 | - `r` - memory factor 35 | - `p` - parallelization factor 36 | - `length` - output buffer length 37 | 38 | ## USAGE 39 | 40 | ```js 41 | const scrypt = require('scrypt') 42 | console.log(scrypt(Buffer.from('password'), Buffer.from('salt'), 262144, 1, 8, 32)) 43 | // 44 | ``` 45 | 46 | ## LICENSE 47 | 48 | This library is free and open-source software released under the MIT license. 49 | 50 | [1]: https://www.tarsnap.com/scrypt.html 51 | [2]: https://github.com/felixrieseberg/windows-build-tools 52 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | build: off 3 | skip_tags: true 4 | environment: 5 | matrix: 6 | - nodejs_version: "4" 7 | - nodejs_version: "5" 8 | - nodejs_version: "6" 9 | - nodejs_version: "7" 10 | - nodejs_version: "8" 11 | platform: 12 | - x86 13 | - x64 14 | install: 15 | - git submodule update --init --recursive 16 | - ps: Install-Product node $env:nodejs_version $env:platform 17 | - npm --global install npm@latest 18 | - set PATH=%APPDATA%\npm;%APPVEYOR_BUILD_FOLDER%\node_modules\.bin;%PATH% 19 | - npm install 20 | - for /f %%i in ('node -v') do set exact_nodejs_version=%%i 21 | test_script: 22 | - npm run unit 23 | -------------------------------------------------------------------------------- /benchmarks/browser.js: -------------------------------------------------------------------------------- 1 | const scrypt = require('../js') 2 | 3 | const now = () => new Date().getTime() 4 | 5 | setTimeout(() => { 6 | const node = document.getElementById('scrypt') 7 | node.innerHTML = 'Start...' 8 | 9 | const ts1 = now() 10 | console.log(scrypt(Buffer.from('password'), Buffer.from('salt'), 16384, 8, 1, 32).toString('hex')) 11 | const delta1 = now() - ts1 12 | 13 | node.innerHTML += `
current: ${delta1}ms` 14 | }, 1000) 15 | -------------------------------------------------------------------------------- /benchmarks/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scrypt browser benchmark 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /benchmarks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scrypt-benchmark", 3 | "version": "0.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "./node_modules/.bin/browserify browser.js -o browser.bundle.js && firefox ./index.html", 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "browserify": "^14.4.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [{ 3 | "target_name": "scrypt", 4 | "sources": [ 5 | "./src/addon.cc", 6 | "./src/scrypt/lib/crypto/crypto_scrypt.c", 7 | "./src/scrypt/lib/crypto/crypto_scrypt_smix.c", 8 | "./src/scrypt/lib/crypto/crypto_scrypt_smix_sse2.c", 9 | "./src/scrypt/libcperciva/util/insecure_memzero.c", 10 | "./src/scrypt/libcperciva/util/warnp.c" 11 | ], 12 | "include_dirs": [ 13 | "/usr/local/include", 14 | " 0 && value % 1 === 0 8 | } 9 | 10 | module.exports = function (scrypt) { 11 | return function (password, salt, N, r, p, length) { 12 | if (!Buffer.isBuffer(password)) throw new TypeError('"password" must be a Buffer instance') 13 | if (!Buffer.isBuffer(salt)) throw new TypeError('"salt" must be a Buffer instance') 14 | if (!isValidNumber(N)) throw new TypeError('"N" should be positive finite integer') 15 | if (!isValidNumber(r)) throw new TypeError('"r" should be positive finite integer') 16 | if (!isValidNumber(p)) throw new TypeError('"p" should be positive finite integer') 17 | if (!isValidNumber(length)) throw new TypeError('"length" should be positive finite integer') 18 | 19 | if (((N & (N - 1)) !== 0) || (N < 2)) { 20 | throw new RangeError('"N" must be a power of 2 greater than 1') 21 | } 22 | 23 | if (N > buffer.kMaxLength / 128 / r) { 24 | throw new RangeError('"N" is too large') 25 | } 26 | 27 | if (r > buffer.kMaxLength / 128 / p || r > buffer.kMaxLength / 256) { 28 | throw new RangeError('"r" is too large') 29 | } 30 | 31 | if (r * p >= (1 << 30)) { 32 | throw new RangeError('"r" * "p" must be less than 2^30') 33 | } 34 | 35 | if (length > buffer.kMaxLength) { 36 | throw new RangeError('"length" argument must not be larger than ' + buffer.kMaxLength) 37 | } 38 | 39 | return scrypt(password, salt, N, r, p, length) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/scrypt.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var crypto = require('crypto') 3 | 4 | /** 5 | * Compute scrypt(password, salt, N, r, p, buflen) and return buffer with length bytes. 6 | * The parameters r, p must satisfy r * p < 2^30. 7 | * The parameter N must be a power of 2. 8 | */ 9 | module.exports = function (password, salt, N, r, p, length) { 10 | var XY = Buffer.alloc(256 * r) 11 | var V = Buffer.alloc(128 * r * N) 12 | 13 | /* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */ 14 | var B = crypto.pbkdf2Sync(password, salt, 1, p * 128 * r, 'sha256') 15 | 16 | /* 2: for i = 0 to p - 1 do */ 17 | for (var i = 0; i < p; ++i) { 18 | /* 3: B_i <-- MF(B_i, N) */ 19 | smix(B, i * 128 * r, r, N, V, XY) 20 | } 21 | 22 | /* 5: DK <-- PBKDF2(P, B, 1, dkLen) */ 23 | return crypto.pbkdf2Sync(password, B, 1, length, 'sha256') 24 | } 25 | 26 | /** 27 | * smix(B, Bi, r, N, V, XY): 28 | * Compute B = SMix_r(B, N). The input B must be 128r bytes in length; the 29 | * temporary storage V must be 128rN bytes in length; the temporary storage 30 | * XY must be 256r bytes in length. The value N must be a power of 2. 31 | */ 32 | function smix (B, Bi, r, N, V, XY) { 33 | var Xi = 0 34 | var Yi = 128 * r 35 | 36 | /* 1: X <-- B */ 37 | blockcopy(B, Bi, XY, Xi, 128 * r) 38 | 39 | /* 2: for i = 0 to N - 1 do */ 40 | for (var i = 0; i < N; i++) { 41 | /* 3: V_i <-- X */ 42 | blockcopy(XY, Xi, V, i * 128 * r, 128 * r) 43 | 44 | /* 4: X <-- H(X) */ 45 | blockmixSalsa8(XY, Xi, Yi, r) 46 | } 47 | 48 | /* 6: for i = 0 to N - 1 do */ 49 | for (i = 0; i < N; i++) { 50 | /* 7: j <-- Integerify(X) mod N */ 51 | var offset = Xi + (2 * r - 1) * 64 52 | var j = XY.readUInt32LE(offset) & (N - 1) 53 | 54 | /* 8: X <-- H(X \xor V_j) */ 55 | blockxor(XY, Xi, V, j * 128 * r, 128 * r) 56 | blockmixSalsa8(XY, Xi, Yi, r) 57 | } 58 | 59 | /* 10: B' <-- X */ 60 | blockcopy(XY, Xi, B, Bi, 128 * r) 61 | } 62 | 63 | /** 64 | * blockmixSalsa8(BY, Bi, Yi, r): 65 | * Compute B = BlockMix_{salsa20/8, r}(B). The input B must be 128r bytes in 66 | * length; the temporary space Y must also be the same size. 67 | */ 68 | function blockmixSalsa8 (BY, Bi, Yi, r) { 69 | var X = Buffer.allocUnsafe(64) 70 | 71 | /* 1: X <-- B_{2r - 1} */ 72 | blockcopy(BY, Bi + (2 * r - 1) * 64, X, 0, 64) 73 | 74 | /* 2: for i = 0 to 2r - 1 do */ 75 | for (var i = 0; i < 2 * r; ++i) { 76 | /* 3: X <-- H(X \xor B_i) */ 77 | blockxor(X, 0, BY, Bi + i * 64, 64) 78 | salsa20(X) 79 | 80 | /* 4: Y_i <-- X */ 81 | blockcopy(X, 0, BY, Yi + i * 64, 64) 82 | } 83 | 84 | /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ 85 | for (i = 0; i < r; ++i) blockcopy(BY, Yi + (i * 2) * 64, BY, Bi + i * 64, 64) 86 | for (i = 0; i < r; ++i) blockcopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64) 87 | } 88 | 89 | /** 90 | * salsa20(B): 91 | * Apply the salsa20/8 core to the provided block. 92 | */ 93 | function salsa20 (B) { 94 | var x = new Array(16) 95 | 96 | for (var i = 0, s = 0; i < 16; ++i, s += 4) { 97 | x[i] = B[s + 3] << 24 | B[s + 2] << 16 | B[s + 1] << 8 | B[s] 98 | } 99 | 100 | for (i = 8; i > 0; i -= 2) { 101 | /* Operate on columns. */ 102 | x[4] ^= rotl(x[0] + x[12], 7) 103 | x[8] ^= rotl(x[4] + x[0], 9) 104 | x[12] ^= rotl(x[8] + x[4], 13) 105 | x[0] ^= rotl(x[12] + x[8], 18) 106 | x[9] ^= rotl(x[5] + x[1], 7) 107 | x[13] ^= rotl(x[9] + x[5], 9) 108 | x[1] ^= rotl(x[13] + x[9], 13) 109 | x[5] ^= rotl(x[1] + x[13], 18) 110 | x[14] ^= rotl(x[10] + x[6], 7) 111 | x[2] ^= rotl(x[14] + x[10], 9) 112 | x[6] ^= rotl(x[2] + x[14], 13) 113 | x[10] ^= rotl(x[6] + x[2], 18) 114 | x[3] ^= rotl(x[15] + x[11], 7) 115 | x[7] ^= rotl(x[3] + x[15], 9) 116 | x[11] ^= rotl(x[7] + x[3], 13) 117 | x[15] ^= rotl(x[11] + x[7], 18) 118 | /* Operate on rows. */ 119 | x[1] ^= rotl(x[0] + x[3], 7) 120 | x[2] ^= rotl(x[1] + x[0], 9) 121 | x[3] ^= rotl(x[2] + x[1], 13) 122 | x[0] ^= rotl(x[3] + x[2], 18) 123 | x[6] ^= rotl(x[5] + x[4], 7) 124 | x[7] ^= rotl(x[6] + x[5], 9) 125 | x[4] ^= rotl(x[7] + x[6], 13) 126 | x[5] ^= rotl(x[4] + x[7], 18) 127 | x[11] ^= rotl(x[10] + x[9], 7) 128 | x[8] ^= rotl(x[11] + x[10], 9) 129 | x[9] ^= rotl(x[8] + x[11], 13) 130 | x[10] ^= rotl(x[9] + x[8], 18) 131 | x[12] ^= rotl(x[15] + x[14], 7) 132 | x[13] ^= rotl(x[12] + x[15], 9) 133 | x[14] ^= rotl(x[13] + x[12], 13) 134 | x[15] ^= rotl(x[14] + x[13], 18) 135 | } 136 | 137 | for (i = 0, s = 0; i < 16; ++i, s += 4) { 138 | var carry = B[s] + (x[i] & 0xff) 139 | B[s] = carry & 0xff 140 | 141 | carry = (carry >> 8) + B[s + 1] + (x[i] >> 8 & 0xff) 142 | B[s + 1] = carry & 0xff 143 | 144 | carry = (carry >> 8) + B[s + 2] + (x[i] >> 16 & 0xff) 145 | B[s + 2] = carry & 0xff 146 | 147 | carry = (carry >> 8) + B[s + 3] + (x[i] >> 24 & 0xff) 148 | B[s + 3] = carry & 0xff 149 | } 150 | } 151 | 152 | function blockxor (S, Si, D, Di, length) { 153 | for (var i = 0; i < length; ++i) S[Si + i] ^= D[Di + i] 154 | } 155 | 156 | function blockcopy (S, Si, D, Di, length) { 157 | S.copy(D, Di, Si, Si + length) 158 | } 159 | 160 | function rotl (a, b) { 161 | return (a << b) | (a >>> (32 - b)) 162 | } 163 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scrypt", 3 | "version": "0.0.0", 4 | "description": "Scrypt library (bindings and purejs)", 5 | "keywords": [ 6 | "scrypt" 7 | ], 8 | "bugs": { 9 | "url": "https://github.com/cryptocoinjs/scrypt/issues" 10 | }, 11 | "license": "MIT", 12 | "contributors": [ 13 | "Kirill Fomichev (https://github.com/fanatid)" 14 | ], 15 | "files": [ 16 | "lib", 17 | "src", 18 | "binding.gyp", 19 | "bindings.js", 20 | "index.js", 21 | "js.js" 22 | ], 23 | "main": "./index.js", 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/cryptocoinjs/scrypt.git" 27 | }, 28 | "scripts": { 29 | "install": "npm run rebuild || echo \"Scrypt bindings compilation fail. Pure JS implementation will be used.\"", 30 | "lint": "standard", 31 | "rebuild": "node-gyp rebuild", 32 | "test": "npm run lint && npm run unit", 33 | "test:browser": "karma start karma.conf.js", 34 | "unit": "tape test/index.js" 35 | }, 36 | "dependencies": { 37 | "bindings": "^1.2.1", 38 | "nan": "^2.2.1", 39 | "safe-buffer": "^5.1.0" 40 | }, 41 | "devDependencies": { 42 | "browserify": "^14.4.0", 43 | "karma": "^1.3.0", 44 | "karma-browserify": "^5.0.4", 45 | "karma-chrome-launcher": "^2.0.0", 46 | "karma-detect-browsers": "^2.1.0", 47 | "karma-env-preprocessor": "^0.1.1", 48 | "karma-firefox-launcher": "^1.0.0", 49 | "karma-tap": "^3.1.1", 50 | "standard": "^10.0.2", 51 | "tape": "^4.5.1" 52 | }, 53 | "engines": { 54 | "node": ">=4.0.0" 55 | }, 56 | "gypfile": true, 57 | "browser": { 58 | "./index.js": "./js.js" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/addon.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern "C" { 5 | #include 6 | } 7 | 8 | NAN_METHOD(scrypt) { 9 | v8::Local password = info[0].As(); 10 | v8::Local salt = info[1].As(); 11 | v8::Local N = info[2].As(); 12 | v8::Local r = info[3].As(); 13 | v8::Local p = info[4].As(); 14 | v8::Local length = info[5].As(); 15 | v8::Local buf = node::Buffer::New(v8::Isolate::GetCurrent(), (uint64_t) length->IntegerValue()).ToLocalChecked(); 16 | 17 | int err = crypto_scrypt( 18 | (const uint8_t *) node::Buffer::Data(password), node::Buffer::Length(password), 19 | (const uint8_t *) node::Buffer::Data(salt), node::Buffer::Length(salt), 20 | (uint64_t) N->IntegerValue(), 21 | (uint32_t) r->IntegerValue(), 22 | (uint32_t) p->IntegerValue(), 23 | (uint8_t *) node::Buffer::Data(buf), node::Buffer::Length(buf) 24 | ); 25 | if (err == -1) return Nan::ThrowError("Unknow error"); 26 | 27 | info.GetReturnValue().Set(buf); 28 | } 29 | 30 | void Init(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE exports, Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE module) { 31 | Nan::Export(module, "exports", scrypt); 32 | } 33 | 34 | NODE_MODULE(scrypt, Init) 35 | -------------------------------------------------------------------------------- /src/config-mock.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptocoinjs/scrypt/cb4bd5ea6d13e18b5a6306ffeecfbff97f9498d7/src/config-mock.h -------------------------------------------------------------------------------- /src/win/sha256.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "insecure_memzero.h" 6 | #include "sysendian.h" 7 | 8 | #include "sha256.h" 9 | 10 | /* 11 | * Encode a length len/4 vector of (uint32_t) into a length len vector of 12 | * (uint8_t) in big-endian form. Assumes len is a multiple of 4. 13 | */ 14 | static void 15 | be32enc_vect(uint8_t * dst, const uint32_t * src, size_t len) 16 | { 17 | size_t i; 18 | 19 | /* Sanity-check. */ 20 | assert(len % 4 == 0); 21 | 22 | /* Encode vector, one word at a time. */ 23 | for (i = 0; i < len / 4; i++) 24 | be32enc(dst + i * 4, src[i]); 25 | } 26 | 27 | /* 28 | * Decode a big-endian length len vector of (uint8_t) into a length 29 | * len/4 vector of (uint32_t). Assumes len is a multiple of 4. 30 | */ 31 | static void 32 | be32dec_vect(uint32_t * dst, const uint8_t * src, size_t len) 33 | { 34 | size_t i; 35 | 36 | /* Sanity-check. */ 37 | assert(len % 4 == 0); 38 | 39 | /* Decode vector, one word at a time. */ 40 | for (i = 0; i < len / 4; i++) 41 | dst[i] = be32dec(src + i * 4); 42 | } 43 | 44 | /* SHA256 round constants. */ 45 | static const uint32_t Krnd[64] = { 46 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 47 | 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 48 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 49 | 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 50 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 51 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 52 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 53 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 54 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 55 | 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 56 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 57 | 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 58 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 59 | 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 60 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 61 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 62 | }; 63 | 64 | /* Elementary functions used by SHA256 */ 65 | #define Ch(x, y, z) ((x & (y ^ z)) ^ z) 66 | #define Maj(x, y, z) ((x & (y | z)) | (y & z)) 67 | #define SHR(x, n) (x >> n) 68 | #define ROTR(x, n) ((x >> n) | (x << (32 - n))) 69 | #define S0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) 70 | #define S1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) 71 | #define s0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) 72 | #define s1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) 73 | 74 | /* SHA256 round function */ 75 | #define RND(a, b, c, d, e, f, g, h, k) \ 76 | h += S1(e) + Ch(e, f, g) + k; \ 77 | d += h; \ 78 | h += S0(a) + Maj(a, b, c); 79 | 80 | /* Adjusted round function for rotating state */ 81 | #define RNDr(S, W, i, ii) \ 82 | RND(S[(64 - i) % 8], S[(65 - i) % 8], \ 83 | S[(66 - i) % 8], S[(67 - i) % 8], \ 84 | S[(68 - i) % 8], S[(69 - i) % 8], \ 85 | S[(70 - i) % 8], S[(71 - i) % 8], \ 86 | W[i + ii] + Krnd[i + ii]) 87 | 88 | /* Message schedule computation */ 89 | #define MSCH(W, ii, i) \ 90 | W[i + ii + 16] = s1(W[i + ii + 14]) + W[i + ii + 9] + s0(W[i + ii + 1]) + W[i + ii] 91 | 92 | /* 93 | * SHA256 block compression function. The 256-bit state is transformed via 94 | * the 512-bit input block to produce a new state. 95 | */ 96 | static void 97 | SHA256_Transform(uint32_t state[8], 98 | const uint8_t block[64], 99 | uint32_t W[64], uint32_t S[8]) 100 | { 101 | int i; 102 | 103 | /* 1. Prepare the first part of the message schedule W. */ 104 | be32dec_vect(W, block, 64); 105 | 106 | /* 2. Initialize working variables. */ 107 | memcpy(S, state, 32); 108 | 109 | /* 3. Mix. */ 110 | for (i = 0; i < 64; i += 16) { 111 | RNDr(S, W, 0, i); 112 | RNDr(S, W, 1, i); 113 | RNDr(S, W, 2, i); 114 | RNDr(S, W, 3, i); 115 | RNDr(S, W, 4, i); 116 | RNDr(S, W, 5, i); 117 | RNDr(S, W, 6, i); 118 | RNDr(S, W, 7, i); 119 | RNDr(S, W, 8, i); 120 | RNDr(S, W, 9, i); 121 | RNDr(S, W, 10, i); 122 | RNDr(S, W, 11, i); 123 | RNDr(S, W, 12, i); 124 | RNDr(S, W, 13, i); 125 | RNDr(S, W, 14, i); 126 | RNDr(S, W, 15, i); 127 | 128 | if (i == 48) 129 | break; 130 | MSCH(W, 0, i); 131 | MSCH(W, 1, i); 132 | MSCH(W, 2, i); 133 | MSCH(W, 3, i); 134 | MSCH(W, 4, i); 135 | MSCH(W, 5, i); 136 | MSCH(W, 6, i); 137 | MSCH(W, 7, i); 138 | MSCH(W, 8, i); 139 | MSCH(W, 9, i); 140 | MSCH(W, 10, i); 141 | MSCH(W, 11, i); 142 | MSCH(W, 12, i); 143 | MSCH(W, 13, i); 144 | MSCH(W, 14, i); 145 | MSCH(W, 15, i); 146 | } 147 | 148 | /* 4. Mix local working variables into global state. */ 149 | for (i = 0; i < 8; i++) 150 | state[i] += S[i]; 151 | } 152 | 153 | static const uint8_t PAD[64] = { 154 | 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 155 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 156 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 157 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 158 | }; 159 | 160 | /* Add padding and terminating bit-count. */ 161 | static void 162 | SHA256_Pad(SHA256_CTX * ctx, uint32_t tmp32[72]) 163 | { 164 | size_t r; 165 | 166 | /* Figure out how many bytes we have buffered. */ 167 | r = (ctx->count >> 3) & 0x3f; 168 | 169 | /* Pad to 56 mod 64, transforming if we finish a block en route. */ 170 | if (r < 56) { 171 | /* Pad to 56 mod 64. */ 172 | memcpy(&ctx->buf[r], PAD, 56 - r); 173 | } else { 174 | /* Finish the current block and mix. */ 175 | memcpy(&ctx->buf[r], PAD, 64 - r); 176 | SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]); 177 | 178 | /* The start of the final block is all zeroes. */ 179 | memset(&ctx->buf[0], 0, 56); 180 | } 181 | 182 | /* Add the terminating bit-count. */ 183 | be64enc(&ctx->buf[56], ctx->count); 184 | 185 | /* Mix in the final block. */ 186 | SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]); 187 | } 188 | 189 | /* Magic initialization constants. */ 190 | static const uint32_t initial_state[8] = { 191 | 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 192 | 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 193 | }; 194 | 195 | /** 196 | * SHA256_Init(ctx): 197 | * Initialize the SHA256 context ${ctx}. 198 | */ 199 | void 200 | SHA256_Init(SHA256_CTX * ctx) 201 | { 202 | 203 | /* Zero bits processed so far. */ 204 | ctx->count = 0; 205 | 206 | /* Initialize state. */ 207 | memcpy(ctx->state, initial_state, sizeof(initial_state)); 208 | } 209 | 210 | /** 211 | * SHA256_Update(ctx, in, len): 212 | * Input ${len} bytes from ${in} into the SHA256 context ${ctx}. 213 | */ 214 | static void 215 | _SHA256_Update(SHA256_CTX * ctx, const void * in, size_t len, 216 | uint32_t tmp32[72]) 217 | { 218 | uint32_t r; 219 | const uint8_t * src = in; 220 | 221 | /* Return immediately if we have nothing to do. */ 222 | if (len == 0) 223 | return; 224 | 225 | /* Number of bytes left in the buffer from previous updates. */ 226 | r = (ctx->count >> 3) & 0x3f; 227 | 228 | /* Update number of bits. */ 229 | ctx->count += (uint64_t)(len) << 3; 230 | 231 | /* Handle the case where we don't need to perform any transforms. */ 232 | if (len < 64 - r) { 233 | memcpy(&ctx->buf[r], src, len); 234 | return; 235 | } 236 | 237 | /* Finish the current block. */ 238 | memcpy(&ctx->buf[r], src, 64 - r); 239 | SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]); 240 | src += 64 - r; 241 | len -= 64 - r; 242 | 243 | /* Perform complete blocks. */ 244 | while (len >= 64) { 245 | SHA256_Transform(ctx->state, src, &tmp32[0], &tmp32[64]); 246 | src += 64; 247 | len -= 64; 248 | } 249 | 250 | /* Copy left over data into buffer. */ 251 | memcpy(ctx->buf, src, len); 252 | } 253 | 254 | /* Wrapper function for intermediate-values sanitization. */ 255 | void 256 | SHA256_Update(SHA256_CTX * ctx, const void * in, size_t len) 257 | { 258 | uint32_t tmp32[72]; 259 | 260 | /* Call the real function. */ 261 | _SHA256_Update(ctx, in, len, tmp32); 262 | 263 | /* Clean the stack. */ 264 | insecure_memzero(tmp32, 288); 265 | } 266 | 267 | /** 268 | * SHA256_Final(digest, ctx): 269 | * Output the SHA256 hash of the data input to the context ${ctx} into the 270 | * buffer ${digest}. 271 | */ 272 | static void 273 | _SHA256_Final(uint8_t digest[32], SHA256_CTX * ctx, 274 | uint32_t tmp32[72]) 275 | { 276 | 277 | /* Add padding. */ 278 | SHA256_Pad(ctx, tmp32); 279 | 280 | /* Write the hash. */ 281 | be32enc_vect(digest, ctx->state, 32); 282 | } 283 | 284 | /* Wrapper function for intermediate-values sanitization. */ 285 | void 286 | SHA256_Final(uint8_t digest[32], SHA256_CTX * ctx) 287 | { 288 | uint32_t tmp32[72]; 289 | 290 | /* Call the real function. */ 291 | _SHA256_Final(digest, ctx, tmp32); 292 | 293 | /* Clear the context state. */ 294 | insecure_memzero(ctx, sizeof(SHA256_CTX)); 295 | 296 | /* Clean the stack. */ 297 | insecure_memzero(tmp32, 288); 298 | } 299 | 300 | /** 301 | * SHA256_Buf(in, len, digest): 302 | * Compute the SHA256 hash of ${len} bytes from ${in} and write it to ${digest}. 303 | */ 304 | void 305 | SHA256_Buf(const void * in, size_t len, uint8_t digest[32]) 306 | { 307 | SHA256_CTX ctx; 308 | uint32_t tmp32[72]; 309 | 310 | SHA256_Init(&ctx); 311 | _SHA256_Update(&ctx, in, len, tmp32); 312 | _SHA256_Final(digest, &ctx, tmp32); 313 | 314 | /* Clean the stack. */ 315 | insecure_memzero(&ctx, sizeof(SHA256_CTX)); 316 | insecure_memzero(tmp32, 288); 317 | } 318 | 319 | /** 320 | * HMAC_SHA256_Init(ctx, K, Klen): 321 | * Initialize the HMAC-SHA256 context ${ctx} with ${Klen} bytes of key from 322 | * ${K}. 323 | */ 324 | static void 325 | _HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen, 326 | uint32_t tmp32[72], uint8_t pad[64], 327 | uint8_t khash[32]) 328 | { 329 | const uint8_t * K = _K; 330 | size_t i; 331 | 332 | /* If Klen > 64, the key is really SHA256(K). */ 333 | if (Klen > 64) { 334 | SHA256_Init(&ctx->ictx); 335 | _SHA256_Update(&ctx->ictx, K, Klen, tmp32); 336 | _SHA256_Final(khash, &ctx->ictx, tmp32); 337 | K = khash; 338 | Klen = 32; 339 | } 340 | 341 | /* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */ 342 | SHA256_Init(&ctx->ictx); 343 | memset(pad, 0x36, 64); 344 | for (i = 0; i < Klen; i++) 345 | pad[i] ^= K[i]; 346 | _SHA256_Update(&ctx->ictx, pad, 64, tmp32); 347 | 348 | /* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */ 349 | SHA256_Init(&ctx->octx); 350 | memset(pad, 0x5c, 64); 351 | for (i = 0; i < Klen; i++) 352 | pad[i] ^= K[i]; 353 | _SHA256_Update(&ctx->octx, pad, 64, tmp32); 354 | } 355 | 356 | /* Wrapper function for intermediate-values sanitization. */ 357 | void 358 | HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen) 359 | { 360 | uint32_t tmp32[72]; 361 | uint8_t pad[64]; 362 | uint8_t khash[32]; 363 | 364 | /* Call the real function. */ 365 | _HMAC_SHA256_Init(ctx, _K, Klen, tmp32, pad, khash); 366 | 367 | /* Clean the stack. */ 368 | insecure_memzero(tmp32, 288); 369 | insecure_memzero(khash, 32); 370 | insecure_memzero(pad, 64); 371 | } 372 | 373 | /** 374 | * HMAC_SHA256_Update(ctx, in, len): 375 | * Input ${len} bytes from ${in} into the HMAC-SHA256 context ${ctx}. 376 | */ 377 | static void 378 | _HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void * in, size_t len, 379 | uint32_t tmp32[72]) 380 | { 381 | 382 | /* Feed data to the inner SHA256 operation. */ 383 | _SHA256_Update(&ctx->ictx, in, len, tmp32); 384 | } 385 | 386 | /* Wrapper function for intermediate-values sanitization. */ 387 | void 388 | HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void * in, size_t len) 389 | { 390 | uint32_t tmp32[72]; 391 | 392 | /* Call the real function. */ 393 | _HMAC_SHA256_Update(ctx, in, len, tmp32); 394 | 395 | /* Clean the stack. */ 396 | insecure_memzero(tmp32, 288); 397 | } 398 | 399 | /** 400 | * HMAC_SHA256_Final(digest, ctx): 401 | * Output the HMAC-SHA256 of the data input to the context ${ctx} into the 402 | * buffer ${digest}. 403 | */ 404 | static void 405 | _HMAC_SHA256_Final(uint8_t digest[32], HMAC_SHA256_CTX * ctx, 406 | uint32_t tmp32[72], uint8_t ihash[32]) 407 | { 408 | 409 | /* Finish the inner SHA256 operation. */ 410 | _SHA256_Final(ihash, &ctx->ictx, tmp32); 411 | 412 | /* Feed the inner hash to the outer SHA256 operation. */ 413 | _SHA256_Update(&ctx->octx, ihash, 32, tmp32); 414 | 415 | /* Finish the outer SHA256 operation. */ 416 | _SHA256_Final(digest, &ctx->octx, tmp32); 417 | } 418 | 419 | /* Wrapper function for intermediate-values sanitization. */ 420 | void 421 | HMAC_SHA256_Final(uint8_t digest[32], HMAC_SHA256_CTX * ctx) 422 | { 423 | uint32_t tmp32[72]; 424 | uint8_t ihash[32]; 425 | 426 | /* Call the real function. */ 427 | _HMAC_SHA256_Final(digest, ctx, tmp32, ihash); 428 | 429 | /* Clean the stack. */ 430 | insecure_memzero(tmp32, 288); 431 | insecure_memzero(ihash, 32); 432 | } 433 | 434 | /** 435 | * HMAC_SHA256_Buf(K, Klen, in, len, digest): 436 | * Compute the HMAC-SHA256 of ${len} bytes from ${in} using the key ${K} of 437 | * length ${Klen}, and write the result to ${digest}. 438 | */ 439 | void 440 | HMAC_SHA256_Buf(const void * K, size_t Klen, const void * in, size_t len, 441 | uint8_t digest[32]) 442 | { 443 | HMAC_SHA256_CTX ctx; 444 | uint32_t tmp32[72]; 445 | uint8_t tmp8[96]; 446 | 447 | _HMAC_SHA256_Init(&ctx, K, Klen, tmp32, &tmp8[0], &tmp8[64]); 448 | _HMAC_SHA256_Update(&ctx, in, len, tmp32); 449 | _HMAC_SHA256_Final(digest, &ctx, tmp32, &tmp8[0]); 450 | 451 | /* Clean the stack. */ 452 | insecure_memzero(&ctx, sizeof(HMAC_SHA256_CTX)); 453 | insecure_memzero(tmp32, 288); 454 | insecure_memzero(tmp8, 96); 455 | } 456 | 457 | /** 458 | * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): 459 | * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and 460 | * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). 461 | */ 462 | void 463 | PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, 464 | size_t saltlen, uint64_t c, uint8_t * buf, size_t dkLen) 465 | { 466 | HMAC_SHA256_CTX Phctx, PShctx, hctx; 467 | uint32_t tmp32[72]; 468 | uint8_t tmp8[96]; 469 | size_t i; 470 | uint8_t ivec[4]; 471 | uint8_t U[32]; 472 | uint8_t T[32]; 473 | uint64_t j; 474 | int k; 475 | size_t clen; 476 | 477 | /* Sanity-check. */ 478 | assert(dkLen <= 32 * (size_t)(UINT32_MAX)); 479 | 480 | /* Compute HMAC state after processing P. */ 481 | _HMAC_SHA256_Init(&Phctx, passwd, passwdlen, 482 | tmp32, &tmp8[0], &tmp8[64]); 483 | 484 | /* Compute HMAC state after processing P and S. */ 485 | memcpy(&PShctx, &Phctx, sizeof(HMAC_SHA256_CTX)); 486 | _HMAC_SHA256_Update(&PShctx, salt, saltlen, tmp32); 487 | 488 | /* Iterate through the blocks. */ 489 | for (i = 0; i * 32 < dkLen; i++) { 490 | /* Generate INT(i + 1). */ 491 | be32enc(ivec, (uint32_t)(i + 1)); 492 | 493 | /* Compute U_1 = PRF(P, S || INT(i)). */ 494 | memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX)); 495 | _HMAC_SHA256_Update(&hctx, ivec, 4, tmp32); 496 | _HMAC_SHA256_Final(U, &hctx, tmp32, tmp8); 497 | 498 | /* T_i = U_1 ... */ 499 | memcpy(T, U, 32); 500 | 501 | for (j = 2; j <= c; j++) { 502 | /* Compute U_j. */ 503 | memcpy(&hctx, &Phctx, sizeof(HMAC_SHA256_CTX)); 504 | _HMAC_SHA256_Update(&hctx, U, 32, tmp32); 505 | _HMAC_SHA256_Final(U, &hctx, tmp32, tmp8); 506 | 507 | /* ... xor U_j ... */ 508 | for (k = 0; k < 32; k++) 509 | T[k] ^= U[k]; 510 | } 511 | 512 | /* Copy as many bytes as necessary into buf. */ 513 | clen = dkLen - i * 32; 514 | if (clen > 32) 515 | clen = 32; 516 | memcpy(&buf[i * 32], T, clen); 517 | } 518 | 519 | /* Clean the stack. */ 520 | insecure_memzero(&Phctx, sizeof(HMAC_SHA256_CTX)); 521 | insecure_memzero(&PShctx, sizeof(HMAC_SHA256_CTX)); 522 | insecure_memzero(&hctx, sizeof(HMAC_SHA256_CTX)); 523 | insecure_memzero(tmp32, 288); 524 | insecure_memzero(tmp8, 96); 525 | insecure_memzero(U, 32); 526 | insecure_memzero(T, 32); 527 | } 528 | -------------------------------------------------------------------------------- /src/win/sys/mman.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sys/mman.h 3 | * mman-win32 4 | */ 5 | 6 | #ifndef _SYS_MMAN_H_ 7 | #define _SYS_MMAN_H_ 8 | 9 | #ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. 10 | #define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. 11 | #endif 12 | 13 | /* All the headers include this file. */ 14 | #ifndef _MSC_VER 15 | #include <_mingw.h> 16 | #else 17 | #include "inttypes.h" 18 | #endif 19 | 20 | #include 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | #define PROT_NONE 0 27 | #define PROT_READ 1 28 | #define PROT_WRITE 2 29 | #define PROT_EXEC 4 30 | 31 | #define MAP_FILE 0 32 | #define MAP_SHARED 1 33 | #define MAP_PRIVATE 2 34 | #define MAP_TYPE 0xf 35 | #define MAP_FIXED 0x10 36 | #define MAP_ANONYMOUS 0x20 37 | #define MAP_ANON MAP_ANONYMOUS 38 | 39 | #define MAP_FAILED ((void *)-1) 40 | 41 | /* Flags for msync. */ 42 | #define MS_ASYNC 1 43 | #define MS_SYNC 2 44 | #define MS_INVALIDATE 4 45 | 46 | void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); 47 | int munmap(void *addr, size_t len); 48 | int mprotect(void *addr, size_t len, int prot); 49 | int msync(void *addr, size_t len, int flags); 50 | int mlock(const void *addr, size_t len); 51 | int munlock(const void *addr, size_t len); 52 | 53 | #ifdef __cplusplus 54 | }; 55 | #endif 56 | 57 | #endif /* _SYS_MMAN_H_ */ 58 | -------------------------------------------------------------------------------- /src/win/unistd.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use script' 2 | var test = require('tape') 3 | 4 | function main (name, scrypt) { 5 | test(name, (t) => { 6 | t.test('params', (t) => { 7 | t.end() 8 | }) 9 | 10 | t.test('vectors', (t) => { 11 | var vectors = require('./vectors').slice(0, 3) // because 4rd is too expensive 12 | for (var i = 0; i < vectors.length; ++i) { 13 | var vector = vectors[i] 14 | var output = scrypt( 15 | Buffer.from(vector.password), 16 | Buffer.from(vector.salt), 17 | vector.N, 18 | vector.r, 19 | vector.p, 20 | vector.output.length / 2) 21 | 22 | t.equal(output.toString('hex'), vector.output) 23 | } 24 | 25 | t.end() 26 | }) 27 | 28 | t.end() 29 | }) 30 | } 31 | 32 | if (!process.browser) main('bindings', require('../bindings')) 33 | main('purejs', require('../js')) 34 | -------------------------------------------------------------------------------- /test/vectors.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "password": "", 3 | "salt": "", 4 | "N": 16, 5 | "r": 1, 6 | "p": 1, 7 | "output": "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906" 8 | }, { 9 | "password": "password", 10 | "salt": "NaCl", 11 | "N": 1024, 12 | "r": 8, 13 | "p": 16, 14 | "output": "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640" 15 | }, { 16 | "password": "pleaseletmein", 17 | "salt": "SodiumChloride", 18 | "N": 16384, 19 | "r": 8, 20 | "p": 1, 21 | "output": "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887" 22 | }, { 23 | "password": "pleaseletmein", 24 | "salt": "SodiumChloride", 25 | "N": 1048576, 26 | "r": 8, 27 | "p": 1, 28 | "output": "2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4" 29 | }] 30 | --------------------------------------------------------------------------------