Skip to content
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!: refactor codebase #407

Merged
merged 9 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/@types/basic.ts

This file was deleted.

12 changes: 0 additions & 12 deletions src/@types/handshake-interface.ts

This file was deleted.

49 changes: 0 additions & 49 deletions src/@types/handshake.ts

This file was deleted.

10 changes: 0 additions & 10 deletions src/@types/libp2p.ts

This file was deleted.

24 changes: 17 additions & 7 deletions src/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import { type Uint8ArrayList } from 'uint8arraylist'
import type { bytes32 } from './@types/basic.js'
import type { Hkdf } from './@types/handshake.js'
import type { KeyPair } from './@types/libp2p.js'
import type { ICrypto, KeyPair } from './types.js'
import type { Uint8ArrayList } from 'uint8arraylist'

/** Underlying crypto implementation, meant to be overridable */
export interface ICryptoInterface {
hashSHA256(data: Uint8Array | Uint8ArrayList): Uint8Array

getHKDF(ck: bytes32, ikm: Uint8Array): Hkdf
getHKDF(ck: Uint8Array, ikm: Uint8Array): [Uint8Array, Uint8Array, Uint8Array]

generateX25519KeyPair(): KeyPair
generateX25519KeyPairFromSeed(seed: Uint8Array): KeyPair
generateX25519SharedKey(privateKey: Uint8Array | Uint8ArrayList, publicKey: Uint8Array | Uint8ArrayList): Uint8Array

chaCha20Poly1305Encrypt(plaintext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: bytes32): Uint8ArrayList | Uint8Array
chaCha20Poly1305Decrypt(ciphertext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: bytes32, dst?: Uint8Array): Uint8ArrayList | Uint8Array | null
chaCha20Poly1305Encrypt(plaintext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array): Uint8ArrayList | Uint8Array
chaCha20Poly1305Decrypt(ciphertext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array, dst?: Uint8Array): Uint8ArrayList | Uint8Array
wemeetagain marked this conversation as resolved.
Show resolved Hide resolved
}

export function wrapCrypto (crypto: ICryptoInterface): ICrypto {
return {
generateKeypair: crypto.generateX25519KeyPair,
dh: (keypair, publicKey) => crypto.generateX25519SharedKey(keypair.privateKey, publicKey).subarray(0, 32),
encrypt: crypto.chaCha20Poly1305Encrypt,
decrypt: crypto.chaCha20Poly1305Decrypt,
hash: crypto.hashSHA256,
hkdf: crypto.getHKDF
}
}
16 changes: 9 additions & 7 deletions src/crypto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { digest } from '@chainsafe/as-sha256'
import { Uint8ArrayList } from 'uint8arraylist'
import { isElectronMain } from 'wherearewe'
import { pureJsCrypto } from './js.js'
import type { KeyPair } from '../@types/libp2p.js'
import type { ICryptoInterface } from '../crypto.js'
import type { KeyPair } from '../types.js'

const ctx = newInstance()
const asImpl = new ChaCha20Poly1305(ctx)
Expand Down Expand Up @@ -38,7 +38,7 @@ const nodeCrypto: Pick<ICryptoInterface, 'hashSHA256' | 'chaCha20Poly1305Encrypt
const final = cipher.final()
const tag = cipher.getAuthTag()

return Buffer.concat([updated, tag, final], updated.byteLength + tag.byteLength + final.byteLength)
return Buffer.concat([updated, final, tag], updated.byteLength + final.byteLength + tag.byteLength)
}

const output = new Uint8ArrayList()
Expand Down Expand Up @@ -112,7 +112,11 @@ const asCrypto: Pick<ICryptoInterface, 'hashSHA256' | 'chaCha20Poly1305Encrypt'
return asImpl.seal(k, nonce, plaintext.subarray(), ad)
},
chaCha20Poly1305Decrypt (ciphertext, nonce, ad, k, dst) {
return asImpl.open(k, nonce, ciphertext.subarray(), ad, dst)
const plaintext = asImpl.open(k, nonce, ciphertext.subarray(), ad, dst)
if (!plaintext) {
throw new Error('Invalid chacha20poly1305 decryption')
}
return plaintext
}
}

Expand Down Expand Up @@ -181,8 +185,7 @@ export const defaultCrypto: ICryptoInterface = {
publicKey
], X25519_PREFIX.byteLength + publicKey.byteLength)
} else {
publicKey.prepend(X25519_PREFIX)
publicKey = publicKey.subarray()
publicKey = new Uint8ArrayList(X25519_PREFIX, publicKey).subarray()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should write a test to ensure that this state is consistent

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a regression test?
This fixes a bug where the input Uint8ArrayList was mutated. When that input Uint8ArrayList is part of the noise state, then that mutation breaks things.

}

if (privateKey instanceof Uint8Array) {
Expand All @@ -191,8 +194,7 @@ export const defaultCrypto: ICryptoInterface = {
privateKey
], PKCS8_PREFIX.byteLength + privateKey.byteLength)
} else {
privateKey.prepend(PKCS8_PREFIX)
privateKey = privateKey.subarray()
privateKey = new Uint8ArrayList(PKCS8_PREFIX, privateKey).subarray()
}

return crypto.diffieHellman({
Expand Down
10 changes: 4 additions & 6 deletions src/crypto/js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ import { chacha20poly1305 } from '@noble/ciphers/chacha'
import { x25519 } from '@noble/curves/ed25519'
import { extract, expand } from '@noble/hashes/hkdf'
import { sha256 } from '@noble/hashes/sha256'
import type { 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 type { KeyPair } from '../types.js'
import type { Uint8ArrayList } from 'uint8arraylist'

export const pureJsCrypto: ICryptoInterface = {
hashSHA256 (data: Uint8Array | Uint8ArrayList): Uint8Array {
return sha256(data.subarray())
},

getHKDF (ck: bytes32, ikm: Uint8Array): Hkdf {
getHKDF (ck: Uint8Array, ikm: Uint8Array): [Uint8Array, Uint8Array, Uint8Array] {
const prk = extract(sha256, ikm, ck)
const okmU8Array = expand(sha256, prk, undefined, 96)
const okm = okmU8Array
Expand Down Expand Up @@ -48,11 +46,11 @@ export const pureJsCrypto: ICryptoInterface = {
return x25519.getSharedSecret(privateKey.subarray(), publicKey.subarray())
},

chaCha20Poly1305Encrypt (plaintext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: bytes32): Uint8Array {
chaCha20Poly1305Encrypt (plaintext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array): Uint8Array {
return chacha20poly1305(k, nonce, ad).encrypt(plaintext.subarray())
},

chaCha20Poly1305Decrypt (ciphertext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: bytes32, dst?: Uint8Array): Uint8Array | null {
chaCha20Poly1305Decrypt (ciphertext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array, dst?: Uint8Array): Uint8Array {
return chacha20poly1305(k, nonce, ad).decrypt(ciphertext.subarray(), dst)
}
}
64 changes: 8 additions & 56 deletions src/encoder.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Uint8ArrayList } from 'uint8arraylist'
import { alloc as uint8ArrayAlloc, allocUnsafe as uint8ArrayAllocUnsafe } from 'uint8arrays/alloc'
import type { bytes } from './@types/basic.js'
import type { MessageBuffer } from './@types/handshake.js'
import { type Uint8ArrayList } from 'uint8arraylist'
import { allocUnsafe as uint8ArrayAllocUnsafe } from 'uint8arrays/alloc'
import type { LengthDecoderFunction } from 'it-length-prefixed'

export const uint16BEEncode = (value: number): Uint8Array => {
const target = uint8ArrayAllocUnsafe(2)
new DataView(target.buffer, target.byteOffset, target.byteLength).setUint16(0, value, false)
target[0] = value >> 8
target[1] = value
return target
}
uint16BEEncode.bytes = 2
Expand All @@ -15,59 +14,12 @@ export const uint16BEDecode: LengthDecoderFunction = (data: Uint8Array | Uint8Ar
if (data.length < 2) throw RangeError('Could not decode int16BE')

if (data instanceof Uint8Array) {
return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint16(0, false)
let value = 0
value += data[0] << 8
value += data[1]
return value
}

return data.getUint16(0)
}
uint16BEDecode.bytes = 2

// Note: IK and XX encoder usage is opposite (XX uses in stages encode0 where IK uses encode1)

export function encode0 (message: MessageBuffer): Uint8ArrayList {
return new Uint8ArrayList(message.ne, message.ciphertext)
}

export function encode1 (message: MessageBuffer): Uint8ArrayList {
return new Uint8ArrayList(message.ne, message.ns, message.ciphertext)
}

export function encode2 (message: MessageBuffer): Uint8ArrayList {
return new Uint8ArrayList(message.ns, message.ciphertext)
}

export function decode0 (input: bytes): MessageBuffer {
if (input.length < 32) {
throw new Error('Cannot decode stage 0 MessageBuffer: length less than 32 bytes.')
}

return {
ne: input.subarray(0, 32),
ciphertext: input.subarray(32, input.length),
ns: uint8ArrayAlloc(0)
}
}

export function decode1 (input: bytes): MessageBuffer {
if (input.length < 80) {
throw new Error('Cannot decode stage 1 MessageBuffer: length less than 80 bytes.')
}

return {
ne: input.subarray(0, 32),
ns: input.subarray(32, 80),
ciphertext: input.subarray(80, input.length)
}
}

export function decode2 (input: bytes): MessageBuffer {
if (input.length < 48) {
throw new Error('Cannot decode stage 2 MessageBuffer: length less than 48 bytes.')
}

return {
ne: uint8ArrayAlloc(0),
ns: input.subarray(0, 48),
ciphertext: input.subarray(48, input.length)
}
}
Loading
Loading