Livt packages

Livt.Crypto

Version 1.0.5

Fixed-size cryptographic components for Livt, including AES, ChaCha20, SHA, MACs, and key derivation.

Published files (69)

src/aead/ChaCha20Poly1305.lvt

namespace Livt.Crypto.Aead
using Livt.Crypto.Stream
using Livt.Crypto.Mac


// ChaCha20-Poly1305 AEAD (RFC 8439 §2.8).
//
// Block size is 64 bytes, matching the native ChaCha20 keystream block.
// Poly1305 is fed internally in 4 x 16-byte sub-blocks per data block.
//
// Usage:
//   1. SetKey(key[32])
//   2. SetNonce(nonce[12])         -- derives poly_key (counter=0), resets Poly1305,
//                                     resets ChaCha20 data counter to 1
//   3. FeedAAD(block[16])          -- feed one 16-byte AAD chunk to Poly1305
//   4. LoadBlock(block[64]); EncryptBlock()   -- or DecryptBlock()
//      GetBlock() returns ciphertext (or plaintext)
//   5. FinalizeTag(aadLenBytes, dataLenBytes)
//      GetTag() returns the 16-byte Poly1305 tag
//      VerifyTag(expected) returns 1 on match, 0 on mismatch
//
// Partial final blocks: the current component API accepts only full 64-byte
// data blocks. Build a higher-level adapter before exposing variable-length
// messages to callers.
//
// AAD is fed 16 bytes at a time; the caller pads partial final AAD to 16 bytes.
// No PRNG is embedded; nonce uniqueness is the caller's responsibility.
component ChaCha20Poly1305
{
    public const KEY_SIZE:   int = 32
    public const BLOCK_SIZE: int = 64
    public const NONCE_SIZE: int = 12
    public const TAG_SIZE:   int = 16

    chacha:   ChaCha20
    poly:     Poly1305

    blk:    byte[64]
    outBlk: byte[64]
    tag:    byte[16]

    new()
    {
        this.chacha = new ChaCha20()
        this.poly   = new Poly1305()
    }

    // -----------------------------------------------------------------------
    // Public API
    // -----------------------------------------------------------------------

    public fn SetKey(key: byte[32])
    {
        this.chacha.SetKey(key)
    }

    // SetNonce: derives the Poly1305 one-time key (ChaCha20 block with counter=0),
    // sets it as the Poly1305 key, then resets the ChaCha20 counter to 1 for
    // subsequent data encryption/decryption.
    public fn SetNonce(nonce: byte[12])
    {
        // Build counter block: nonce || 0x00 0x00 0x00 0x00 (counter = 0, LE).
        var ctr0: byte[16] = [
            nonce[0],  nonce[1],  nonce[2],  nonce[3],
            nonce[4],  nonce[5],  nonce[6],  nonce[7],
            nonce[8],  nonce[9],  nonce[10], nonce[11],
            0x00, 0x00, 0x00, 0x00]
        this.chacha.SetCounter(ctr0)

        // Encrypt a 64-byte zero block to obtain the keystream for counter=0.
        var zeroBlk: byte[64] = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
        this.chacha.LoadBlock(zeroBlk)
        this.chacha.CryptBlock()
        // First 32 bytes of the keystream = Poly1305 one-time key.
        // Get the full block into a local before slicing out the Poly1305 key.
        var ksBlk: byte[64] = this.chacha.GetBlock()
        var polyKey: byte[32] = [
            ksBlk[0],  ksBlk[1],  ksBlk[2],  ksBlk[3],
            ksBlk[4],  ksBlk[5],  ksBlk[6],  ksBlk[7],
            ksBlk[8],  ksBlk[9],  ksBlk[10], ksBlk[11],
            ksBlk[12], ksBlk[13], ksBlk[14], ksBlk[15],
            ksBlk[16], ksBlk[17], ksBlk[18], ksBlk[19],
            ksBlk[20], ksBlk[21], ksBlk[22], ksBlk[23],
            ksBlk[24], ksBlk[25], ksBlk[26], ksBlk[27],
            ksBlk[28], ksBlk[29], ksBlk[30], ksBlk[31]]
        this.poly.SetKey(polyKey)

        // Reset ChaCha20 counter to 1 (LE): nonce || 0x01 0x00 0x00 0x00.
        var ctr1: byte[16] = [
            nonce[0],  nonce[1],  nonce[2],  nonce[3],
            nonce[4],  nonce[5],  nonce[6],  nonce[7],
            nonce[8],  nonce[9],  nonce[10], nonce[11],
            0x01, 0x00, 0x00, 0x00]
        this.chacha.SetCounter(ctr1)
    }

    // FeedAAD: feed one 16-byte AAD block to Poly1305.
    // Caller pads partial final AAD block with zeros.
    public fn FeedAAD(aadBlock: byte[16])
    {
        this.poly.ProcessBlock(aadBlock)
    }

    public fn LoadBlock(block: byte[64])
    {
        for (var i = 0; i < 64; i++)
        {
            this.blk[i] = block[i]
        }
    }

    // EncryptBlock: encrypt a full 64-byte plaintext block.
    // Feeds all 4 × 16-byte ciphertext sub-blocks to Poly1305.
    public fn EncryptBlock()
    {
        // Encrypt the full 64-byte block with ChaCha20 (counter auto-increments).
        this.chacha.LoadBlock(this.blk)
        this.chacha.CryptBlock()

        // Write ciphertext to outBlk.
        var c0:  byte = this.chacha.GetByte(0);  var c1:  byte = this.chacha.GetByte(1)
        var c2:  byte = this.chacha.GetByte(2);  var c3:  byte = this.chacha.GetByte(3)
        var c4:  byte = this.chacha.GetByte(4);  var c5:  byte = this.chacha.GetByte(5)
        var c6:  byte = this.chacha.GetByte(6);  var c7:  byte = this.chacha.GetByte(7)
        var c8:  byte = this.chacha.GetByte(8);  var c9:  byte = this.chacha.GetByte(9)
        var c10: byte = this.chacha.GetByte(10); var c11: byte = this.chacha.GetByte(11)
        var c12: byte = this.chacha.GetByte(12); var c13: byte = this.chacha.GetByte(13)
        var c14: byte = this.chacha.GetByte(14); var c15: byte = this.chacha.GetByte(15)
        var c16: byte = this.chacha.GetByte(16); var c17: byte = this.chacha.GetByte(17)
        var c18: byte = this.chacha.GetByte(18); var c19: byte = this.chacha.GetByte(19)
        var c20: byte = this.chacha.GetByte(20); var c21: byte = this.chacha.GetByte(21)
        var c22: byte = this.chacha.GetByte(22); var c23: byte = this.chacha.GetByte(23)
        var c24: byte = this.chacha.GetByte(24); var c25: byte = this.chacha.GetByte(25)
        var c26: byte = this.chacha.GetByte(26); var c27: byte = this.chacha.GetByte(27)
        var c28: byte = this.chacha.GetByte(28); var c29: byte = this.chacha.GetByte(29)
        var c30: byte = this.chacha.GetByte(30); var c31: byte = this.chacha.GetByte(31)
        var c32: byte = this.chacha.GetByte(32); var c33: byte = this.chacha.GetByte(33)
        var c34: byte = this.chacha.GetByte(34); var c35: byte = this.chacha.GetByte(35)
        var c36: byte = this.chacha.GetByte(36); var c37: byte = this.chacha.GetByte(37)
        var c38: byte = this.chacha.GetByte(38); var c39: byte = this.chacha.GetByte(39)
        var c40: byte = this.chacha.GetByte(40); var c41: byte = this.chacha.GetByte(41)
        var c42: byte = this.chacha.GetByte(42); var c43: byte = this.chacha.GetByte(43)
        var c44: byte = this.chacha.GetByte(44); var c45: byte = this.chacha.GetByte(45)
        var c46: byte = this.chacha.GetByte(46); var c47: byte = this.chacha.GetByte(47)
        var c48: byte = this.chacha.GetByte(48); var c49: byte = this.chacha.GetByte(49)
        var c50: byte = this.chacha.GetByte(50); var c51: byte = this.chacha.GetByte(51)
        var c52: byte = this.chacha.GetByte(52); var c53: byte = this.chacha.GetByte(53)
        var c54: byte = this.chacha.GetByte(54); var c55: byte = this.chacha.GetByte(55)
        var c56: byte = this.chacha.GetByte(56); var c57: byte = this.chacha.GetByte(57)
        var c58: byte = this.chacha.GetByte(58); var c59: byte = this.chacha.GetByte(59)
        var c60: byte = this.chacha.GetByte(60); var c61: byte = this.chacha.GetByte(61)
        var c62: byte = this.chacha.GetByte(62); var c63: byte = this.chacha.GetByte(63)

        this.outBlk[0]  = c0;  this.outBlk[1]  = c1;  this.outBlk[2]  = c2;  this.outBlk[3]  = c3
        this.outBlk[4]  = c4;  this.outBlk[5]  = c5;  this.outBlk[6]  = c6;  this.outBlk[7]  = c7
        this.outBlk[8]  = c8;  this.outBlk[9]  = c9;  this.outBlk[10] = c10; this.outBlk[11] = c11
        this.outBlk[12] = c12; this.outBlk[13] = c13; this.outBlk[14] = c14; this.outBlk[15] = c15
        this.outBlk[16] = c16; this.outBlk[17] = c17; this.outBlk[18] = c18; this.outBlk[19] = c19
        this.outBlk[20] = c20; this.outBlk[21] = c21; this.outBlk[22] = c22; this.outBlk[23] = c23
        this.outBlk[24] = c24; this.outBlk[25] = c25; this.outBlk[26] = c26; this.outBlk[27] = c27
        this.outBlk[28] = c28; this.outBlk[29] = c29; this.outBlk[30] = c30; this.outBlk[31] = c31
        this.outBlk[32] = c32; this.outBlk[33] = c33; this.outBlk[34] = c34; this.outBlk[35] = c35
        this.outBlk[36] = c36; this.outBlk[37] = c37; this.outBlk[38] = c38; this.outBlk[39] = c39
        this.outBlk[40] = c40; this.outBlk[41] = c41; this.outBlk[42] = c42; this.outBlk[43] = c43
        this.outBlk[44] = c44; this.outBlk[45] = c45; this.outBlk[46] = c46; this.outBlk[47] = c47
        this.outBlk[48] = c48; this.outBlk[49] = c49; this.outBlk[50] = c50; this.outBlk[51] = c51
        this.outBlk[52] = c52; this.outBlk[53] = c53; this.outBlk[54] = c54; this.outBlk[55] = c55
        this.outBlk[56] = c56; this.outBlk[57] = c57; this.outBlk[58] = c58; this.outBlk[59] = c59
        this.outBlk[60] = c60; this.outBlk[61] = c61; this.outBlk[62] = c62; this.outBlk[63] = c63

        // Feed 4 × 16-byte sub-blocks of ciphertext to Poly1305.
        var sub0: byte[16] = [c0,  c1,  c2,  c3,  c4,  c5,  c6,  c7,  c8,  c9,  c10, c11, c12, c13, c14, c15]
        var sub1: byte[16] = [c16, c17, c18, c19, c20, c21, c22, c23, c24, c25, c26, c27, c28, c29, c30, c31]
        var sub2: byte[16] = [c32, c33, c34, c35, c36, c37, c38, c39, c40, c41, c42, c43, c44, c45, c46, c47]
        var sub3: byte[16] = [c48, c49, c50, c51, c52, c53, c54, c55, c56, c57, c58, c59, c60, c61, c62, c63]
        this.poly.ProcessBlock(sub0)
        this.poly.ProcessBlock(sub1)
        this.poly.ProcessBlock(sub2)
        this.poly.ProcessBlock(sub3)
    }

    // DecryptBlock: feed the 64-byte ciphertext block to Poly1305 first,
    // then decrypt with ChaCha20.
    public fn DecryptBlock()
    {
        var c0:  byte = this.blk[0];  var c1:  byte = this.blk[1];  var c2:  byte = this.blk[2];  var c3:  byte = this.blk[3]
        var c4:  byte = this.blk[4];  var c5:  byte = this.blk[5];  var c6:  byte = this.blk[6];  var c7:  byte = this.blk[7]
        var c8:  byte = this.blk[8];  var c9:  byte = this.blk[9];  var c10: byte = this.blk[10]; var c11: byte = this.blk[11]
        var c12: byte = this.blk[12]; var c13: byte = this.blk[13]; var c14: byte = this.blk[14]; var c15: byte = this.blk[15]
        var c16: byte = this.blk[16]; var c17: byte = this.blk[17]; var c18: byte = this.blk[18]; var c19: byte = this.blk[19]
        var c20: byte = this.blk[20]; var c21: byte = this.blk[21]; var c22: byte = this.blk[22]; var c23: byte = this.blk[23]
        var c24: byte = this.blk[24]; var c25: byte = this.blk[25]; var c26: byte = this.blk[26]; var c27: byte = this.blk[27]
        var c28: byte = this.blk[28]; var c29: byte = this.blk[29]; var c30: byte = this.blk[30]; var c31: byte = this.blk[31]
        var c32: byte = this.blk[32]; var c33: byte = this.blk[33]; var c34: byte = this.blk[34]; var c35: byte = this.blk[35]
        var c36: byte = this.blk[36]; var c37: byte = this.blk[37]; var c38: byte = this.blk[38]; var c39: byte = this.blk[39]
        var c40: byte = this.blk[40]; var c41: byte = this.blk[41]; var c42: byte = this.blk[42]; var c43: byte = this.blk[43]
        var c44: byte = this.blk[44]; var c45: byte = this.blk[45]; var c46: byte = this.blk[46]; var c47: byte = this.blk[47]
        var c48: byte = this.blk[48]; var c49: byte = this.blk[49]; var c50: byte = this.blk[50]; var c51: byte = this.blk[51]
        var c52: byte = this.blk[52]; var c53: byte = this.blk[53]; var c54: byte = this.blk[54]; var c55: byte = this.blk[55]
        var c56: byte = this.blk[56]; var c57: byte = this.blk[57]; var c58: byte = this.blk[58]; var c59: byte = this.blk[59]
        var c60: byte = this.blk[60]; var c61: byte = this.blk[61]; var c62: byte = this.blk[62]; var c63: byte = this.blk[63]

        // Feed 4 × 16-byte sub-blocks of ciphertext to Poly1305.
        var sub0: byte[16] = [c0,  c1,  c2,  c3,  c4,  c5,  c6,  c7,  c8,  c9,  c10, c11, c12, c13, c14, c15]
        var sub1: byte[16] = [c16, c17, c18, c19, c20, c21, c22, c23, c24, c25, c26, c27, c28, c29, c30, c31]
        var sub2: byte[16] = [c32, c33, c34, c35, c36, c37, c38, c39, c40, c41, c42, c43, c44, c45, c46, c47]
        var sub3: byte[16] = [c48, c49, c50, c51, c52, c53, c54, c55, c56, c57, c58, c59, c60, c61, c62, c63]
        this.poly.ProcessBlock(sub0)
        this.poly.ProcessBlock(sub1)
        this.poly.ProcessBlock(sub2)
        this.poly.ProcessBlock(sub3)

        // Decrypt: XOR ciphertext with ChaCha20 keystream (counter auto-increments).
        this.chacha.LoadBlock(this.blk)
        this.chacha.CryptBlock()

        var p0:  byte = this.chacha.GetByte(0);  var p1:  byte = this.chacha.GetByte(1)
        var p2:  byte = this.chacha.GetByte(2);  var p3:  byte = this.chacha.GetByte(3)
        var p4:  byte = this.chacha.GetByte(4);  var p5:  byte = this.chacha.GetByte(5)
        var p6:  byte = this.chacha.GetByte(6);  var p7:  byte = this.chacha.GetByte(7)
        var p8:  byte = this.chacha.GetByte(8);  var p9:  byte = this.chacha.GetByte(9)
        var p10: byte = this.chacha.GetByte(10); var p11: byte = this.chacha.GetByte(11)
        var p12: byte = this.chacha.GetByte(12); var p13: byte = this.chacha.GetByte(13)
        var p14: byte = this.chacha.GetByte(14); var p15: byte = this.chacha.GetByte(15)
        var p16: byte = this.chacha.GetByte(16); var p17: byte = this.chacha.GetByte(17)
        var p18: byte = this.chacha.GetByte(18); var p19: byte = this.chacha.GetByte(19)
        var p20: byte = this.chacha.GetByte(20); var p21: byte = this.chacha.GetByte(21)
        var p22: byte = this.chacha.GetByte(22); var p23: byte = this.chacha.GetByte(23)
        var p24: byte = this.chacha.GetByte(24); var p25: byte = this.chacha.GetByte(25)
        var p26: byte = this.chacha.GetByte(26); var p27: byte = this.chacha.GetByte(27)
        var p28: byte = this.chacha.GetByte(28); var p29: byte = this.chacha.GetByte(29)
        var p30: byte = this.chacha.GetByte(30); var p31: byte = this.chacha.GetByte(31)
        var p32: byte = this.chacha.GetByte(32); var p33: byte = this.chacha.GetByte(33)
        var p34: byte = this.chacha.GetByte(34); var p35: byte = this.chacha.GetByte(35)
        var p36: byte = this.chacha.GetByte(36); var p37: byte = this.chacha.GetByte(37)
        var p38: byte = this.chacha.GetByte(38); var p39: byte = this.chacha.GetByte(39)
        var p40: byte = this.chacha.GetByte(40); var p41: byte = this.chacha.GetByte(41)
        var p42: byte = this.chacha.GetByte(42); var p43: byte = this.chacha.GetByte(43)
        var p44: byte = this.chacha.GetByte(44); var p45: byte = this.chacha.GetByte(45)
        var p46: byte = this.chacha.GetByte(46); var p47: byte = this.chacha.GetByte(47)
        var p48: byte = this.chacha.GetByte(48); var p49: byte = this.chacha.GetByte(49)
        var p50: byte = this.chacha.GetByte(50); var p51: byte = this.chacha.GetByte(51)
        var p52: byte = this.chacha.GetByte(52); var p53: byte = this.chacha.GetByte(53)
        var p54: byte = this.chacha.GetByte(54); var p55: byte = this.chacha.GetByte(55)
        var p56: byte = this.chacha.GetByte(56); var p57: byte = this.chacha.GetByte(57)
        var p58: byte = this.chacha.GetByte(58); var p59: byte = this.chacha.GetByte(59)
        var p60: byte = this.chacha.GetByte(60); var p61: byte = this.chacha.GetByte(61)
        var p62: byte = this.chacha.GetByte(62); var p63: byte = this.chacha.GetByte(63)

        this.outBlk[0]  = p0;  this.outBlk[1]  = p1;  this.outBlk[2]  = p2;  this.outBlk[3]  = p3
        this.outBlk[4]  = p4;  this.outBlk[5]  = p5;  this.outBlk[6]  = p6;  this.outBlk[7]  = p7
        this.outBlk[8]  = p8;  this.outBlk[9]  = p9;  this.outBlk[10] = p10; this.outBlk[11] = p11
        this.outBlk[12] = p12; this.outBlk[13] = p13; this.outBlk[14] = p14; this.outBlk[15] = p15
        this.outBlk[16] = p16; this.outBlk[17] = p17; this.outBlk[18] = p18; this.outBlk[19] = p19
        this.outBlk[20] = p20; this.outBlk[21] = p21; this.outBlk[22] = p22; this.outBlk[23] = p23
        this.outBlk[24] = p24; this.outBlk[25] = p25; this.outBlk[26] = p26; this.outBlk[27] = p27
        this.outBlk[28] = p28; this.outBlk[29] = p29; this.outBlk[30] = p30; this.outBlk[31] = p31
        this.outBlk[32] = p32; this.outBlk[33] = p33; this.outBlk[34] = p34; this.outBlk[35] = p35
        this.outBlk[36] = p36; this.outBlk[37] = p37; this.outBlk[38] = p38; this.outBlk[39] = p39
        this.outBlk[40] = p40; this.outBlk[41] = p41; this.outBlk[42] = p42; this.outBlk[43] = p43
        this.outBlk[44] = p44; this.outBlk[45] = p45; this.outBlk[46] = p46; this.outBlk[47] = p47
        this.outBlk[48] = p48; this.outBlk[49] = p49; this.outBlk[50] = p50; this.outBlk[51] = p51
        this.outBlk[52] = p52; this.outBlk[53] = p53; this.outBlk[54] = p54; this.outBlk[55] = p55
        this.outBlk[56] = p56; this.outBlk[57] = p57; this.outBlk[58] = p58; this.outBlk[59] = p59
        this.outBlk[60] = p60; this.outBlk[61] = p61; this.outBlk[62] = p62; this.outBlk[63] = p63
    }

    // FinalizeTag: feed the 16-byte length block (LE64(aadLen) || LE64(dataLen))
    // to Poly1305 as the final block, then finalize the tag.
    // RFC 8439 §2.8: lengths encoded as 64-bit little-endian.
    public fn FinalizeTag(aadLenBytes: int, dataLenBytes: int)
    {
        // Build LE64(aadLenBytes) || LE64(dataLenBytes) as a 16-byte block.
        // For the expected message sizes (< 2^32) only the low 4 bytes are nonzero.
        var la0: int = aadLenBytes  % 256
        var la1: int = (aadLenBytes  / 256) % 256
        var la2: int = (aadLenBytes  / 65536) % 256
        var la3: int = (aadLenBytes  / 16777216) % 256
        var ld0: int = dataLenBytes % 256
        var ld1: int = (dataLenBytes / 256) % 256
        var ld2: int = (dataLenBytes / 65536) % 256
        var ld3: int = (dataLenBytes / 16777216) % 256
        var bla0: byte = la0 as byte; var bla1: byte = la1 as byte
        var bla2: byte = la2 as byte; var bla3: byte = la3 as byte
        var bld0: byte = ld0 as byte; var bld1: byte = ld1 as byte
        var bld2: byte = ld2 as byte; var bld3: byte = ld3 as byte
        var lenBlk: byte[16] = [
            bla0, bla1, bla2, bla3, 0x00, 0x00, 0x00, 0x00,
            bld0, bld1, bld2, bld3, 0x00, 0x00, 0x00, 0x00]
        this.poly.ProcessBlock(lenBlk)
        this.poly.FinalizeTag()
        var t: byte[16] = this.poly.GetTag()
        for (var i = 0; i < 16; i++)
        {
            this.tag[i] = t[i]
        }
    }

    public fn GetBlock() byte[64]
    {
        return this.outBlk
    }

    public fn GetByte(idx: int) byte
    {
        return this.outBlk[idx]
    }

    public fn GetTag() byte[16]
    {
        return this.tag
    }

    // VerifyTag compares the finalized authentication tag without early exit.
    // Returns 1 on match, 0 on mismatch.
    public fn VerifyTag(expected: byte[16]) int
    {
        var diff: byte = 0x00
        for (var i = 0; i < 16; i++)
        {
            var delta: byte = this.tag[i] ^ expected[i]
            diff = diff ^ delta
        }

        var result: int = 0
        if (diff == 0x00)
        {
            result = 1
        }
        return result
    }

    public fn ClearKey()
    {
        this.chacha.ClearKey()
        for (var i = 0; i < 32; i++)
        {
            var zb: byte = 0x00 as byte
            this.blk[i] = zb
        }
        for (var i = 0; i < 32; i++)
        {
            var zb: byte = 0x00 as byte
            this.outBlk[i] = zb
        }
    }
}