-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add sodium-native #161
Closed
Closed
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
728508b
feat: sodium-native as a new crypto implementation
twoeths 0aff337
chore: add unit test for encrypt/decrypt
twoeths 4ac31c4
perf: benchmark stablelib vs sodium-native
twoeths b5e9d4b
chore: fix lint
twoeths 347844a
chore: benchmark different Buffer size
twoeths c977866
chore: fix crypto.js benchmark
twoeths File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* eslint-disable */ | ||
import {stablelib} from '../dist/src/crypto/stablelib.js' | ||
import {sodiumNative} from '../dist/src/crypto/sodium-native.js' | ||
import benchmark from 'benchmark' | ||
|
||
const main = async function () { | ||
const data = Buffer.from('encryptthis encryptthis encryptthis encryptthis') | ||
const nonce = 1000 | ||
const nonceBytes = new Uint8Array(12) | ||
new DataView(nonceBytes.buffer, nonceBytes.byteOffset, nonceBytes.byteLength).setUint32(4, nonce, true) | ||
const key = new Uint8Array(Array.from({length: 32}, () => 1)) | ||
const encrypted = stablelib.chaCha20Poly1305Encrypt(data, nonceBytes, new Uint8Array(0), key) | ||
|
||
for(const {id, crypto} of [ | ||
{id: 'stablelib decrypt', crypto: stablelib}, | ||
{id: 'sodium-native decrypt', crypto: sodiumNative} | ||
]) { | ||
const bench = new benchmark(id, { | ||
fn: async function () { | ||
crypto.chaCha20Poly1305Decrypt(encrypted, nonceBytes, new Uint8Array(0), key) | ||
} | ||
}) | ||
.on('complete', function (stats) { | ||
console.log(String(stats.currentTarget)) | ||
}) | ||
bench.run() | ||
} | ||
|
||
for(const {id, crypto} of [ | ||
{id: 'stablelib encrypt', crypto: stablelib}, | ||
{id: 'sodium-native encrypt', crypto: sodiumNative} | ||
]) { | ||
const bench = new benchmark(id, { | ||
fn: async function () { | ||
crypto.chaCha20Poly1305Encrypt(data, nonceBytes, new Uint8Array(0), key) | ||
} | ||
}) | ||
.on('complete', function (stats) { | ||
console.log(String(stats.currentTarget)) | ||
}) | ||
bench.run() | ||
} | ||
} | ||
|
||
main() |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* eslint-disable @typescript-eslint/naming-convention */ | ||
import type { bytes, bytes32 } from '../@types/basic.js' | ||
import type { Hkdf } from '../@types/handshake.js' | ||
import type { KeyPair } from '../@types/libp2p.js' | ||
import type { ICryptoInterface } from '../crypto.js' | ||
import sodium from 'sodium-native' | ||
|
||
import { concat as uint8ArrayConcat } from 'uint8arrays/concat' | ||
|
||
const { | ||
/* @ts-expect-error */ | ||
crypto_aead_chacha20poly1305_ietf_decrypt, | ||
/* @ts-expect-error */ | ||
crypto_aead_chacha20poly1305_ietf_encrypt, | ||
/* @ts-expect-error */ | ||
crypto_aead_chacha20poly1305_ietf_ABYTES, | ||
crypto_box_keypair, | ||
crypto_box_PUBLICKEYBYTES, | ||
crypto_box_SECRETKEYBYTES, | ||
crypto_box_seed_keypair, | ||
crypto_hash_sha256, | ||
crypto_scalarmult, | ||
crypto_scalarmult_BYTES, | ||
sodium_malloc, | ||
sodium_memzero | ||
} = sodium | ||
|
||
const hkdfBlockLen = 64 | ||
const hkdfHashLen = 32 | ||
const hkdfStep1 = new Uint8Array([0x01]) | ||
const hkdfStep2 = new Uint8Array([0x02]) | ||
const hkdfStep3 = new Uint8Array([0x03]) | ||
const hmacBuffer = sodium_malloc(hkdfBlockLen * 3) | ||
const hmacKey = hmacBuffer.subarray(hkdfBlockLen * 0, hkdfBlockLen) | ||
const hmacOuterKeyPad = hmacBuffer.subarray(hkdfBlockLen, hkdfBlockLen * 2) | ||
const hmacInnerKeyPad = hmacBuffer.subarray(hkdfBlockLen * 2, hkdfBlockLen * 3) | ||
|
||
/* c8 ignore start */ | ||
function hmac (out: Buffer, data: Uint8Array, key: bytes32): void { | ||
if (key.byteLength > hkdfBlockLen) { | ||
crypto_hash_sha256(hmacKey.subarray(0, hkdfHashLen), Buffer.from(key)) | ||
sodium_memzero(hmacKey.subarray(hkdfHashLen)) | ||
} else { | ||
hmacKey.set(key) | ||
sodium_memzero(hmacKey.subarray(key.byteLength)) | ||
} | ||
|
||
for (let i = 0; i < hmacKey.byteLength; i++) { | ||
hmacOuterKeyPad[i] = 0x5c ^ hmacKey[i] | ||
hmacInnerKeyPad[i] = 0x36 ^ hmacKey[i] | ||
} | ||
|
||
crypto_hash_sha256(out, Buffer.from(uint8ArrayConcat([hmacInnerKeyPad, data]))) | ||
sodium_memzero(hmacInnerKeyPad) | ||
crypto_hash_sha256(out, Buffer.from(uint8ArrayConcat([hmacOuterKeyPad, out]))) | ||
sodium_memzero(hmacOuterKeyPad) | ||
} | ||
|
||
export const sodiumNative: ICryptoInterface = { | ||
hashSHA256 (data: Uint8Array): Uint8Array { | ||
const out = sodium_malloc(32) | ||
crypto_hash_sha256(out, Buffer.from(data)) | ||
|
||
return out | ||
}, | ||
getHKDF (ck: bytes32, ikm: Uint8Array): Hkdf { | ||
// Extract | ||
const prk = sodium_malloc(32) | ||
hmac(prk, ikm, ck) | ||
|
||
// Derive | ||
const out = sodium_malloc(hkdfHashLen * 3) | ||
const out1 = out.subarray(0, hkdfHashLen) | ||
const out2 = out.subarray(hkdfHashLen, hkdfHashLen * 2) | ||
const out3 = out.subarray(hkdfHashLen * 2, hkdfHashLen * 3) | ||
hmac(out1, hkdfStep1, prk) | ||
hmac(out2, uint8ArrayConcat([out1, hkdfStep2]), prk) | ||
hmac(out3, uint8ArrayConcat([out2, hkdfStep3]), prk) | ||
|
||
return [ | ||
out.slice(0, hkdfHashLen), | ||
out.slice(hkdfHashLen, hkdfHashLen * 2), | ||
out.slice(hkdfHashLen * 2, hkdfHashLen * 3) | ||
] | ||
}, | ||
generateX25519KeyPair (): KeyPair { | ||
const publicKey = sodium_malloc(crypto_box_PUBLICKEYBYTES) | ||
const privateKey = sodium_malloc(crypto_box_SECRETKEYBYTES) | ||
|
||
crypto_box_keypair(publicKey, privateKey) | ||
|
||
return { publicKey, privateKey } | ||
}, | ||
generateX25519KeyPairFromSeed (seed: Uint8Array): KeyPair { | ||
const publicKey = sodium_malloc(crypto_box_PUBLICKEYBYTES) | ||
const privateKey = sodium_malloc(crypto_box_SECRETKEYBYTES) | ||
|
||
crypto_box_seed_keypair(publicKey, privateKey, Buffer.from(seed)) | ||
|
||
return { publicKey, privateKey } | ||
}, | ||
generateX25519SharedKey (privateKey: Uint8Array, publicKey: Uint8Array): Uint8Array { | ||
const shared = sodium_malloc(crypto_scalarmult_BYTES) | ||
crypto_scalarmult(shared, Buffer.from(privateKey), Buffer.from(publicKey)) | ||
|
||
return shared | ||
}, | ||
chaCha20Poly1305Encrypt (plaintext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32): bytes { | ||
const out = sodium_malloc(plaintext.length + (crypto_aead_chacha20poly1305_ietf_ABYTES as number)) | ||
|
||
crypto_aead_chacha20poly1305_ietf_encrypt(out, plaintext, ad, null, nonce, k) | ||
|
||
return out | ||
}, | ||
chaCha20Poly1305Decrypt (ciphertext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32): bytes | null { | ||
const out = sodium_malloc(ciphertext.length - crypto_aead_chacha20poly1305_ietf_ABYTES) | ||
|
||
try { | ||
crypto_aead_chacha20poly1305_ietf_decrypt(out, null, ciphertext, ad, nonce, k) | ||
} catch (error) { | ||
if ((error as Error).message === 'could not verify data') { | ||
return null | ||
} | ||
|
||
throw error | ||
} | ||
|
||
return out | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { assert } from 'aegir/chai' | ||
import { stablelib } from '../../src/crypto/stablelib.js' | ||
import { sodiumNative } from '../../src/crypto/sodium-native.js' | ||
import { equals as uint8ArrayEquals } from 'uint8arrays/equals' | ||
import type { ICryptoInterface } from '../../src/crypto.js' | ||
|
||
describe('Crypto implementation', () => { | ||
const testCases: Array<{id: string, encrypt: ICryptoInterface, decrypt: ICryptoInterface}> = [ | ||
{ id: 'sodium-native should be able to decrypt from data encrypted by stablelib', decrypt: sodiumNative, encrypt: stablelib }, | ||
{ id: 'sodium-native should be able to decrypt from data encrypted by sodium-native', decrypt: sodiumNative, encrypt: sodiumNative }, | ||
{ id: 'stablelib should be able to decrypt from data encrypted by sodium-native', decrypt: stablelib, encrypt: sodiumNative }, | ||
{ id: 'stablelib should be able to decrypt from data encrypted by stablelib', decrypt: stablelib, encrypt: stablelib } | ||
] | ||
for (const { id, encrypt, decrypt } of testCases) { | ||
it(id, () => { | ||
const data = Buffer.from('encryptthis') | ||
const nonce = 1000 | ||
const nonceBytes = new Uint8Array(12) | ||
new DataView(nonceBytes.buffer, nonceBytes.byteOffset, nonceBytes.byteLength).setUint32(4, nonce, true) | ||
const key = new Uint8Array(Array.from({ length: 32 }, () => 1)) | ||
const encrypted = encrypt.chaCha20Poly1305Encrypt(data, nonceBytes, new Uint8Array(0), key) | ||
const decrypted = decrypt.chaCha20Poly1305Decrypt(encrypted, nonceBytes, new Uint8Array(0), key) | ||
assert(uint8ArrayEquals(decrypted as Uint8Array, Buffer.from('encryptthis'))) | ||
}) | ||
} | ||
}) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tuyennhv please do benchmarks for a range of data sizes. Research what the range of paquet sizes for eth2 objects and bechmark against them, for example: [200, 500, 1000, 1e4, 1e5, 1e6]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dapplion for eth2, most of the chunk size is 0-500, per 100 chunks like that there's a big chunk size at 20000. I updated the benchmark below