mirror of
https://github.com/vacp2p/nim-libp2p.git
synced 2026-01-09 02:38:19 -05:00
feat(mix): crypto (#1687)
This commit is contained in:
53
libp2p/protocols/mix/crypto.nim
Normal file
53
libp2p/protocols/mix/crypto.nim
Normal file
@@ -0,0 +1,53 @@
|
||||
import endians, nimcrypto
|
||||
|
||||
proc aes_ctr*(key, iv, data: openArray[byte]): seq[byte] =
|
||||
## Processes 'data' using AES in CTR mode.
|
||||
## For CTR mode, the same function handles both encryption and decryption.
|
||||
doAssert key.len == 16, "Key must be 16 bytes for AES-128"
|
||||
doAssert iv.len == 16, "IV must be 16 bytes for AES-128"
|
||||
|
||||
var
|
||||
ctx: CTR[aes128]
|
||||
output = newSeq[byte](data.len)
|
||||
|
||||
ctx.init(key, iv)
|
||||
ctx.encrypt(data, output)
|
||||
ctx.clear()
|
||||
|
||||
output
|
||||
|
||||
proc advance_ctr*(iv: var openArray[byte], blocks: uint64) =
|
||||
## Advances the counter in the AES-CTR IV by a specified number of blocks.
|
||||
var counter: uint64
|
||||
bigEndian64(addr counter, addr iv[8])
|
||||
counter += blocks
|
||||
bigEndian64(addr iv[8], addr counter)
|
||||
|
||||
proc aes_ctr_start_index*(key, iv, data: openArray[byte], startIndex: int): seq[byte] =
|
||||
## Encrypts 'data' using AES in CTR mode from startIndex, without processing all preceding data.
|
||||
## For CTR mode, the same function handles both encryption and decryption.
|
||||
doAssert key.len == 16, "Key must be 16 bytes for AES-128"
|
||||
doAssert iv.len == 16, "IV must be 16 bytes for AES-128"
|
||||
doAssert startIndex mod 16 == 0, "Start index must be a multiple of 16"
|
||||
|
||||
var advIV = @iv
|
||||
|
||||
# Advance the counter to the start index
|
||||
let blocksToAdvance = startIndex div 16
|
||||
advance_ctr(advIV, blocksToAdvance.uint64)
|
||||
|
||||
return aes_ctr(key, advIV, data)
|
||||
|
||||
proc sha256_hash*(data: openArray[byte]): array[32, byte] =
|
||||
## hashes 'data' using SHA-256.
|
||||
return sha256.digest(data).data
|
||||
|
||||
proc kdf*(key: openArray[byte]): seq[byte] =
|
||||
## Returns the hash of 'key' truncated to 16 bytes.
|
||||
let hash = sha256_hash(key)
|
||||
return hash[0 .. 15]
|
||||
|
||||
proc hmac*(key, data: openArray[byte]): seq[byte] =
|
||||
## Computes a HMAC for 'data' using given 'key'.
|
||||
let hmac = sha256.hmac(key, data).data
|
||||
return hmac[0 .. 15]
|
||||
52
libp2p/protocols/mix/curve25519.nim
Normal file
52
libp2p/protocols/mix/curve25519.nim
Normal file
@@ -0,0 +1,52 @@
|
||||
import results
|
||||
import bearssl/rand
|
||||
import ../../crypto/curve25519
|
||||
|
||||
const FieldElementSize* = Curve25519KeySize
|
||||
|
||||
type FieldElement* = Curve25519Key
|
||||
|
||||
proc bytesToFieldElement*(bytes: openArray[byte]): Result[FieldElement, string] =
|
||||
## Convert bytes to FieldElement
|
||||
if bytes.len != FieldElementSize:
|
||||
return err("Field element size must be 32 bytes")
|
||||
ok(intoCurve25519Key(bytes))
|
||||
|
||||
proc fieldElementToBytes*(fe: FieldElement): seq[byte] =
|
||||
## Convert FieldElement to bytes
|
||||
fe.getBytes()
|
||||
|
||||
# Generate a random FieldElement
|
||||
proc generateRandomFieldElement*(): Result[FieldElement, string] =
|
||||
let rng = HmacDrbgContext.new()
|
||||
if rng.isNil:
|
||||
return err("Failed to creat HmacDrbgContext with system randomness")
|
||||
ok(Curve25519Key.random(rng[]))
|
||||
|
||||
# Generate a key pair (private key and public key are both FieldElements)
|
||||
proc generateKeyPair*(): Result[tuple[privateKey, publicKey: FieldElement], string] =
|
||||
let privateKey = generateRandomFieldElement().valueOr:
|
||||
return err("Error in private key generation: " & error)
|
||||
|
||||
let publicKey = public(privateKey)
|
||||
ok((privateKey, publicKey))
|
||||
|
||||
proc multiplyPointWithScalars*(
|
||||
point: FieldElement, scalars: openArray[FieldElement]
|
||||
): FieldElement =
|
||||
## Multiply a given Curve25519 point with a set of scalars
|
||||
var res = point
|
||||
for scalar in scalars:
|
||||
Curve25519.mul(res, scalar)
|
||||
res
|
||||
|
||||
proc multiplyBasePointWithScalars*(
|
||||
scalars: openArray[FieldElement]
|
||||
): Result[FieldElement, string] =
|
||||
## Multiply the Curve25519 base point with a set of scalars
|
||||
if scalars.len <= 0:
|
||||
return err("Atleast one scalar must be provided")
|
||||
var res: FieldElement = public(scalars[0]) # Use the predefined base point
|
||||
for i in 1 ..< scalars.len:
|
||||
Curve25519.mul(res, scalars[i]) # Multiply with each scalar
|
||||
ok(res)
|
||||
125
tests/mix/testcrypto.nim
Normal file
125
tests/mix/testcrypto.nim
Normal file
@@ -0,0 +1,125 @@
|
||||
{.used.}
|
||||
|
||||
import nimcrypto, results, unittest
|
||||
import ../../libp2p/protocols/mix/crypto
|
||||
|
||||
suite "cryptographic_functions_tests":
|
||||
test "aes_ctr_encrypt_decrypt":
|
||||
let
|
||||
key = cast[array[16, byte]]("thisis16byteskey")
|
||||
iv = cast[array[16, byte]]("thisis16bytesiv!")
|
||||
data: seq[byte] = cast[seq[byte]]("thisisdata")
|
||||
|
||||
let encrypted = aes_ctr(key, iv, data)
|
||||
let decrypted = aes_ctr(key, iv, encrypted)
|
||||
|
||||
check:
|
||||
data == decrypted
|
||||
data != encrypted
|
||||
|
||||
test "sha256_hash_computation":
|
||||
let
|
||||
data: seq[byte] = cast[seq[byte]]("thisisdata")
|
||||
expectedHashHex =
|
||||
"b53a20ecf0814267a83be82f941778ffda4b85fbf93a07847539f645ff5f1b9b"
|
||||
expectedHash = fromHex(expectedHashHex)
|
||||
hash = sha256_hash(data)
|
||||
|
||||
check hash == expectedHash
|
||||
|
||||
test "kdf_computation":
|
||||
let
|
||||
key: seq[byte] = cast[seq[byte]]("thisiskey")
|
||||
expectedKdfHex = "37c9842d37dc404854428a0a3554dcaa"
|
||||
expectedKdf = fromHex(expectedKdfHex)
|
||||
derivedKey = kdf(key)
|
||||
|
||||
check derivedKey == expectedKdf
|
||||
|
||||
test "hmac_computation":
|
||||
let
|
||||
key: seq[byte] = cast[seq[byte]]("thisiskey")
|
||||
data: seq[byte] = cast[seq[byte]]("thisisdata")
|
||||
expectedHmacHex = "b075dd302655e085d35e8cef5dfdf101"
|
||||
expectedHmac = fromHex(expectedHmacHex)
|
||||
hmacResult = hmac(key, data)
|
||||
|
||||
check hmacResult == expectedHmac
|
||||
|
||||
test "aes_ctr_empty_data":
|
||||
let
|
||||
key = cast[array[16, byte]]("thisis16byteskey")
|
||||
iv = cast[array[16, byte]]("thisis16bytesiv!")
|
||||
emptyData: array[0, byte] = []
|
||||
|
||||
let encrypted = aes_ctr(key, iv, emptyData)
|
||||
let decrypted = aes_ctr(key, iv, encrypted)
|
||||
|
||||
check:
|
||||
emptyData == decrypted
|
||||
emptyData == encrypted
|
||||
|
||||
test "sha256_hash_empty_data":
|
||||
let
|
||||
emptyData: array[0, byte] = []
|
||||
expectedHashHex =
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
expectedHash = fromHex(expectedHashHex)
|
||||
hash = sha256_hash(emptyData)
|
||||
|
||||
check hash == expectedHash
|
||||
|
||||
test "kdf_empty_key":
|
||||
let
|
||||
emptyKey: array[0, byte] = []
|
||||
expectedKdfHex = "e3b0c44298fc1c149afbf4c8996fb924"
|
||||
expectedKdf = fromHex(expectedKdfHex)
|
||||
derivedKey = kdf(emptyKey)
|
||||
|
||||
check derivedKey == expectedKdf
|
||||
|
||||
test "hmac_empty_key_and_data":
|
||||
let
|
||||
emptyKey: array[0, byte] = []
|
||||
emptyData: array[0, byte] = []
|
||||
expectedHmacHex = "b613679a0814d9ec772f95d778c35fc5"
|
||||
expectedHmac = fromHex(expectedHmacHex)
|
||||
hmacResult = hmac(emptyKey, emptyData)
|
||||
|
||||
check hmacResult == expectedHmac
|
||||
|
||||
test "aes_ctr_start_index_zero_index":
|
||||
let
|
||||
key = cast[array[16, byte]]("thisis16byteskey")
|
||||
iv = cast[array[16, byte]]("thisis16bytesiv!")
|
||||
data: seq[byte] = cast[seq[byte]]("thisisdata")
|
||||
startIndex = 0
|
||||
|
||||
let encrypted = aes_ctr_start_index(key, iv, data, startIndex)
|
||||
let expected = aes_ctr(key, iv, data)
|
||||
|
||||
check encrypted == expected
|
||||
|
||||
test "aes_ctr_start_index_empty_data":
|
||||
let
|
||||
key = cast[array[16, byte]]("thisis16byteskey")
|
||||
iv = cast[array[16, byte]]("thisis16bytesiv!")
|
||||
emptyData: array[0, byte] = []
|
||||
startIndex = 0
|
||||
|
||||
let encrypted = aes_ctr_start_index(key, iv, emptyData, startIndex)
|
||||
|
||||
check emptyData == encrypted
|
||||
|
||||
test "aes_ctr_start_index_middle":
|
||||
let
|
||||
key = cast[array[16, byte]]("thisis16byteskey")
|
||||
iv = cast[array[16, byte]]("thisis16bytesiv!")
|
||||
data: seq[byte] = cast[seq[byte]]("thisisverylongdata")
|
||||
startIndex = 16
|
||||
|
||||
let encrypted2 = aes_ctr_start_index(key, iv, data[startIndex ..^ 1], startIndex)
|
||||
let encrypted1 = aes_ctr(key, iv, data[0 .. startIndex - 1])
|
||||
let expected = aes_ctr(key, iv, data)
|
||||
|
||||
check encrypted1 & encrypted2 == expected
|
||||
45
tests/mix/testcurve25519.nim
Normal file
45
tests/mix/testcurve25519.nim
Normal file
@@ -0,0 +1,45 @@
|
||||
{.used.}
|
||||
|
||||
import results, unittest
|
||||
import ../../libp2p/crypto/curve25519
|
||||
import ../../libp2p/protocols/mix/curve25519
|
||||
|
||||
proc isNotZero(key: FieldElement): bool =
|
||||
for byte in key:
|
||||
if byte != 0:
|
||||
return true
|
||||
return false
|
||||
|
||||
suite "curve25519_tests":
|
||||
test "generate_key_pair":
|
||||
let (privateKey, publicKey) = generateKeyPair().expect("generate keypair error")
|
||||
|
||||
check:
|
||||
fieldElementToBytes(privateKey).len == FieldElementSize
|
||||
fieldElementToBytes(publicKey).len == FieldElementSize
|
||||
privateKey.isNotZero()
|
||||
publicKey.isNotZero()
|
||||
|
||||
let derivedPublicKey = multiplyBasePointWithScalars(@[privateKey]).expect(
|
||||
"multiply base point with scalar error"
|
||||
)
|
||||
|
||||
check publicKey == derivedPublicKey
|
||||
|
||||
test "commutativity":
|
||||
let
|
||||
x1 = generateRandomFieldElement().expect("generate random field element error")
|
||||
x2 = generateRandomFieldElement().expect("generate random field element error")
|
||||
res1 = multiplyBasePointWithScalars(@[x1, x2]).expect(
|
||||
"multiply base point with scalar errors"
|
||||
)
|
||||
res2 = multiplyBasePointWithScalars(@[x2, x1]).expect(
|
||||
"multiply base point with scalar errors"
|
||||
)
|
||||
res3 = multiplyPointWithScalars(public(x2), @[x1])
|
||||
res4 = multiplyPointWithScalars(public(x1), @[x2])
|
||||
|
||||
check:
|
||||
res1 == res2
|
||||
res1 == res3
|
||||
res1 == res4
|
||||
@@ -41,3 +41,5 @@ import kademlia/[testencoding, testroutingtable, testfindnode, testputval]
|
||||
|
||||
when defined(libp2p_autotls_support):
|
||||
import testautotls
|
||||
|
||||
import mix/[testcrypto, testcurve25519]
|
||||
|
||||
Reference in New Issue
Block a user