A pure Motoko implementation of the ChaCha20-Poly1305 Authenticated Encryption with Associated Data (AEAD) algorithm.
This library provides a complete implementation of:
- ChaCha20 stream cipher (RFC 8439)
- Poly1305 message authentication code
- ChaCha20-Poly1305 AEAD construction
The implementation follows RFC 8439 specifications and includes comprehensive test coverage with RFC test vectors.
- RFC 8439 compliant ChaCha20-Poly1305 AEAD
- Support for single-block and multi-block encryption
- Authenticated encryption with associated data (AEAD)
- Comprehensive test suite with RFC test vectors
- Performance benchmarking suite
mops add chacha
import Chacha20 "mo:chacha/Chacha";
// Generate key and nonce
let keyBytes = [/* 32 bytes */];
let nonceBytes = [/* 12 bytes */];
let key = Chacha20.keyFromBytes(keyBytes);
let nonce = Chacha20.nonceFromBytes(nonceBytes);
// Encrypt data
let plaintext = [/* your data */];
let ciphertext = Chacha20.encryptMultiBlock(key, 0, nonce, plaintext);
// Decrypt (ChaCha20 is symmetric)
let decrypted = Chacha20.encryptMultiBlock(key, 0, nonce, ciphertext);
import Poly1305 "mo:chacha/Poly1305";
let key = [/* 32 bytes */];
let message = [/* your message */];
// Compute MAC
let tag = Poly1305.mac(key, message);
// Verify MAC
let isValid = Poly1305.verify(tag, expectedTag);
import Chacha20_poly1305 "mo:chacha";
let plaintext = [/* your data */];
let aad = [/* associated data */];
let key = [/* 32 bytes */];
let iv = [/* 8 bytes */];
let constant = [/* 4 bytes */];
// Encrypt with authentication
let (ciphertext, tag) = Chacha20_poly1305.aeadEncrypt(plaintext, aad, key, iv, constant);
// Decrypt and verify
switch (Chacha20_poly1305.aeadDecrypt(ciphertext, tag, aad, key, iv, constant)) {
case (?decrypted) { /* Success */ };
case (null) { /* Authentication failed */ };
};
Function | Description |
---|---|
keyFromBytes([Nat8]) |
Convert 32-byte array to ChaCha20 key |
nonceFromBytes([Nat8]) |
Convert 12-byte array to ChaCha20 nonce |
chachaBlock(key, counter, nonce) |
Generate 64-byte keystream block |
encrypt(key, counter, nonce, plaintext) |
Encrypt single block (≤64 bytes) |
encryptMultiBlock(key, counter, nonce, plaintext) |
Encrypt any size data |
quarterRound(state, a, b, c, d) |
Core ChaCha20 quarter round operation |
Function | Description |
---|---|
mac(key, message) |
Compute Poly1305 MAC tag |
verify(tag1, tag2) |
Constant-time MAC verification |
Function | Description |
---|---|
aeadEncrypt(plaintext, aad, key, iv, constant) |
Authenticated encryption |
aeadDecrypt(ciphertext, tag, aad, key, iv, constant) |
Authenticated decryption |
Performance measurements using IC.countInstructions()
on Internet Computer replica:
Operation | Instructions |
---|---|
Quarter Round | 1,905 |
ChaCha Block (64B) | 180,119 |
Key From Bytes | 9,031 |
Nonce From Bytes | 2,938 |
Size | ChaCha20 | Poly1305 | AEAD |
---|---|---|---|
16B | 11,870 | 52,716 | 114,399 |
64B | 3,328 | 23,146 | 39,322 |
1024B | 3,313 | 13,348 | 17,815 |
1400B | 3,329 | 13,352 | 17,410 |
4096B | 3,312 | 12,847 | 16,807 |
16 | 64 | 256 | 1024 | 1400 | 4096 | 16384 | |
---|---|---|---|---|---|---|---|
ChaCha20 | 5.36 KiB | 5.92 KiB | 22.86 KiB | 89.68 KiB | 123.04 KiB | 356.15 KiB | 1.39 MiB |
Poly1305 | 22 KiB | 38.75 KiB | 102.24 KiB | 358.14 KiB | 489.71 KiB | 1.35 MiB | 5.34 MiB |
AEAD | 48.21 KiB | 65.73 KiB | 147.59 KiB | 471.91 KiB | 630.48 KiB | 1.74 MiB | 6.83 MiB |
- ChaCha20: Consistent ~3,300 instructions/byte for messages ≥64 bytes
- Poly1305: Performance improves with message size (52K to 13K instructions/byte)
- AEAD Overhead: 4-7% coordination cost for medium/large messages
- Optimal Range: 1KB-4KB messages for best efficiency
- Implementation follows RFC 8439 specifications
- Uses constant-time operations where applicable
- Nonce reuse with the same key is cryptographically dangerous!
- Each encryption operation should use a unique nonce
- The presence of the private keys in a canister is not secure as a single malicious node provider could inspect the state of the canister. VetKeys is a more secure alternative but more expensive.
- 20-round ARX (Add-Rotate-XOR) cipher
- 256-bit key, 96-bit nonce, 32-bit counter
- Generates 64-byte keystream blocks
- XOR keystream with plaintext for encryption
- Universal hash function for message authentication
- 256-bit key input
- Operates over prime field 2^130 - 5
- Produces 128-bit authentication tag
- Use ChaCha20 to encrypt plaintext
- Use Poly1305 to authenticate ciphertext + associated data
- Combine for authenticated encryption
- Prioritises for correctness over performance
- Poly1305 field arithmetic is the primary performance bottleneck
- ChaCha20 implementation is well-optimized for Motoko
- Memory usage is predictable and constant per operation
- Performance is significantly slower than other implementations (eg. C or Rust Implementations) :( But still pretty good for motoko
- Small messages (<256 bytes) have high per-byte overhead