Skip to content
!? ReLaX edited this page Apr 8, 2026 · 10 revisions

MASC-256: Memory Accumulator Stack Cipher 256-bit

Complete Technical Documentation — v1.3

Classification: Symmetric Stream Cipher · Custom Research Design
State Width: 256-bit (four 64-bit words)
Intended Use: Academic research, demonstration, security experimentation
Authors: RlxChap2 License: MIT


Table of Contents

  1. Introduction
  2. Notation and Conventions
  3. Internal State: The Accumulator (ACC)
  4. Primitive Operations
  5. Key Initialization — init_acc
  6. Encryption Process
  7. Decryption Process
  8. Security Analysis
  9. Test Vectors
  10. Architecture Diagrams
  11. Comparison with AES-256
  12. Known Limitations and Future Work
  13. Compilation and Execution
  14. API Reference
  15. References

1. Introduction

MASC-256 (Memory Accumulator Stack Cipher, 256-bit) is an experimental symmetric stream cipher whose central design thesis is that dynamic runtime memory behavior can be used as an entropy amplifier to increase diffusion without incurring the fixed-round overhead of block cipher constructions.

Design Goals

Goal Mechanism
High diffusion per byte Accumulator round with aggressive rotations
Memory-dependent keystream Stack and heap state folded into every output byte
Symmetric operation Identical encrypt / decrypt logic; state synchronizes on ciphertext bytes
Side-channel disruption Non-deterministic heap allocation addresses at runtime
Simple, auditable core ~120 lines of portable C99

Cipher Family and Positioning

MASC-256 belongs to the additive stream cipher family, where a keystream is generated from a secret key and XOR'd with plaintext byte-by-byte:

Encrypt:   C[i]  =  M[i] XOR ks[i]
Decrypt:   M[i]  =  C[i] XOR ks[i]

Unlike conventional stream ciphers (RC4, Salsa20, ChaCha20) that evolve state purely through arithmetic, MASC-256 deliberately involves the OS memory allocator and stack frame layout in state evolution, making the keystream partially dependent on process-level memory entropy.

⚠️ Research Note: MASC-256 is a novel design that has not undergone formal peer cryptanalysis. It should not be used to protect sensitive production data without independent security audit. See Section 12.


2. Notation and Conventions

Symbol Meaning
XOR / Bitwise XOR
+ Addition modulo 2^64
ROTL(x, r) Left circular rotation of x by r bits
ROTR(x, r) Right circular rotation of x by r bits
& Bitwise AND
a, b, c, d The four 64-bit words of accumulator state ACC
K Key byte array of length L
M[i] Plaintext byte at position i
C[i] Ciphertext byte at position i
ks[i] Keystream byte at position i
ACC[t] Accumulator state at time-step t
mod Modulo operation

All word arithmetic is implicitly mod 2^64 (natural 64-bit integer overflow).
Constants are derived from fractional parts of irrational numbers, matching the SHA-2 / BLAKE2 IV strategy.


3. Internal State: The Accumulator (ACC)

The entire cipher state is a single 256-bit structure consisting of four 64-bit words:

ACC = ( a , b , c , d )
      |----64----|----64----|----64----|----64----|
                        256 bits total

3.1 Initial Constants

State is seeded with magic numbers derived from fractional parts of irrational constants (same strategy as SHA-512 and BLAKE2):

Word Hex Value Derivation
a0 0x9E3779B97F4A7C15 floor( 2^64 / phi ) where phi = 1.6180… (golden ratio)
b0 0x6A09E667F3BCC909 floor( 2^64 * frac(sqrt(2)) )
c0 0xBB67AE8584CAA73B floor( 2^64 * frac(sqrt(3)) )
d0 0x3C6EF372FE94F82B floor( 2^64 * frac(sqrt(5)) )

frac(x) denotes the fractional part of x. These values are well-distributed and bias-free, providing a neutral starting point for key-dependent initialization.

3.2 State Transition Model

Every processing step — absorbing a key byte or encrypting a plaintext byte — drives a full three-stage state update:

ACC[t+1]  =  accumulator_round(
                 heap_mix(
                     stack_mix( ACC[t], v[t] ),
                 s[t] )
             )

where v[t] and s[t] are step-specific seed values derived from the current input byte and the current accumulator.


4. Primitive Operations

4.1 Bitwise Rotation — ROTL / ROTR

ROTL(x, r)  =  (x << r) | (x >> (64 - r))
ROTR(x, r)  =  (x >> r) | (x << (64 - r))

Equivalently:

ROTL(x, r)  =  ROTR(x, 64 - r)
ROTR(x, r)  =  ROTL(x, 64 - r)

Diffusion property: A single-bit flip in x produces exactly one bit flip in ROTL(x, r), shifted to a different position. When composed with XOR and addition — which mix bits across positions — this ensures a local change propagates to all 64 bit positions across a small number of rounds.

The accumulator_round uses rotation offsets 17, 23, 31, 47 — all prime numbers — to avoid short internal cycles within a 64-bit word.


4.2 Stack Mixing — stack_mix

Purpose: Fold eight transient stack-allocated values into all four accumulator words, introducing data-dependent entropy from the processor call stack.

C Source:

void stack_mix(ACC *acc, uint64_t v) {
    uint64_t stack_buf[8];

    for (int i = 0; i < 8; i++) {
        stack_buf[i] = rotl(v ^ (acc->a + i), i + 1);
        acc->a ^= stack_buf[i];
        acc->b += rotl(stack_buf[i], (i + 3) % 64);
        acc->c ^= acc->b + stack_buf[i];
        acc->d  = rotl(acc->d ^ acc->c, 7);
    }
}

Per-iteration update formulas — for each i from 0 to 7:

S[i]  =  ROTL( v XOR (a + i),  i + 1 )

a  <--  a XOR S[i]
b  <--  b + ROTL( S[i],  (i + 3) mod 64 )
c  <--  c XOR (b + S[i])
d  <--  ROTL( d XOR c,  7 )

Intra-iteration dependency chain:

v, a  -->  S[i]  -->  a'
                  |-->  b'  -->  c'  -->  d'

After all 8 iterations, every output word (a', b', c', d') is a non-linear function of all four input words and the seed v. Full intra-state dependency is guaranteed by iteration 8.

Stack entropy note: stack_buf is allocated at a runtime-determined stack address influenced by ASLR, calling conventions, and current stack depth. The computed values inside the buffer are deterministic given (v, ACC), but their memory addresses introduce microarchitectural variation (cache-line placement, prefetcher behavior) that complicates physical side-channel modelling.


4.3 Heap Mixing — heap_mix

Purpose: Allocate a 512-byte heap buffer at an OS-determined (non-deterministic) address, apply 64 rounds of ARX mixing into the accumulator, then release the buffer.

C Source:

void heap_mix(ACC *acc, uint64_t seed) {
    uint64_t *heap = malloc(64 * sizeof(uint64_t));   // 512 bytes

    for (int i = 0; i < 64; i++) {
        heap[i] = rotl(seed ^ (acc->a + i), (i % 31) + 1);
        acc->a += heap[i];
        acc->b ^= rotl(heap[i], (i % 17) + 3);
        acc->c += acc->b ^ heap[i];
        acc->d ^= rotl(acc->c, (i % 13) + 5);
    }

    free(heap);
}

Per-iteration update formulas — for each i from 0 to 63:

H[i]  =  ROTL( seed XOR (a + i),  (i mod 31) + 1 )

a  <--  a + H[i]
b  <--  b XOR ROTL( H[i],  (i mod 17) + 3 )
c  <--  c + (b XOR H[i])
d  <--  d XOR ROTL( c,  (i mod 13) + 5 )

Rotation schedule — three independent moduli:

Update target Rotation formula Period Range of values
H[i] seed rotation (i mod 31) + 1 31 1 to 31
b update (i mod 17) + 3 17 3 to 19
d update (i mod 13) + 5 13 5 to 17

The three moduli — 31, 17, 13 — are pairwise coprime, so the joint rotation triple (rH, rb, rd) repeats with period:

lcm(31, 17, 13)  =  6851  >>  64 iterations

No two iterations within a single heap_mix call ever use the same rotation triple.

Memory-dependent entropy note: The pointer returned by malloc varies each call due to ASLR and allocator free-list state. The buffer occupies different cache lines on each invocation, making cache-timing-based side-channel attacks significantly harder to mount than classical S-box cache attacks on AES.


4.4 Accumulator Round — accumulator_round

Purpose: Apply a final non-linear ARX (Add-Rotate-XOR) permutation across all four 64-bit state words. Structurally analogous to the permutation step in sponge constructions such as Keccak.

C Source:

void accumulator_round(ACC *acc) {
    acc->a = rotl(acc->a ^ acc->d, 17);
    acc->b = rotl(acc->b + acc->a, 23);
    acc->c = rotl(acc->c ^ acc->b, 31);
    acc->d = rotl(acc->d + acc->c, 47);
}

Update formulas — applied sequentially (each line uses the updated value from the line above):

a  <--  ROTL( a XOR d,  17 )
b  <--  ROTL( b + a,    23 )
c  <--  ROTL( c XOR b,  31 )
d  <--  ROTL( d + c,    47 )

Why these rotation constants?

Constant Property
17 Prime
23 Prime
31 Prime (Mersenne prime: 2^5 - 1)
47 Prime

All four constants are prime, avoiding short internal word cycles. The pattern alternates XOR and addition — operations from two algebraically incompatible groups (GF(2)^64 and Z/2^64 Z) — making linear approximations over either group degrade rapidly.

Single-pass full diffusion trace — a 1-bit difference in a:

Step 1:  ROTL(a XOR d, 17)   -->  1-bit change enters a'
Step 2:  ROTL(b + a', 23)   -->  addition carry propagates change into b'
Step 3:  ROTL(c XOR b', 31) -->  change reaches c'
Step 4:  ROTL(d + c', 47)   -->  change reaches d'
         All 4 words affected after 1 call.

5. Key Initialization — init_acc

5.1 Overview

Key initialization maps a variable-length byte key K[0..L-1] to a unique 256-bit accumulator state. Each key byte drives one complete three-stage state update cycle.

C Source:

void init_acc(ACC *acc, const uint8_t *key, size_t len) {
    acc->a = 0x9E3779B97F4A7C15ULL;
    acc->b = 0x6A09E667F3BCC909ULL;
    acc->c = 0xBB67AE8584CAA73BULL;
    acc->d = 0x3C6EF372FE94F82BULL;

    for (size_t i = 0; i < len; i++) {
        acc->a ^= key[i] + rotl(acc->d, i % 63);   // Key absorption
        stack_mix(acc, key[i]);                       // Stack entropy
        heap_mix(acc, key[i] ^ acc->a);              // Heap entropy
        accumulator_round(acc);                       // Non-linear round
    }
}

5.2 Per-Key-Byte Update — Four Steps

For key byte index i with value K[i]:

Step 1 — Key Absorption into word a:

a  <--  a XOR ( K[i]  +  ROTL(d,  i mod 63) )

Step 2 — Stack Mixing (8 inner iterations):

ACC  <--  stack_mix( ACC,  K[i] )

Step 3 — Heap Mixing with seed = K[i] XOR a (64 inner iterations):

ACC  <--  heap_mix( ACC,  K[i] XOR a )

Step 4 — Non-linear ARX Round:

ACC  <--  accumulator_round( ACC )

5.3 Key Schedule Flow Diagram

  ( a0, b0, c0, d0 )   <-- magic constants
         |
         |  repeat for i = 0, 1, ..., L-1
         v
  +----------------------------------------------+
  |  STEP 1 — Key Absorption                     |
  |  a <-- a XOR (K[i] + ROTL(d, i mod 63))     |
  +----------------------+-----------------------+
                         |
                         v
  +----------------------------------------------+
  |  STEP 2 — stack_mix(ACC, K[i])              |
  |  8 iterations of ARX on stack buffer        |
  +----------------------+-----------------------+
                         |
                         v
  +----------------------------------------------+
  |  STEP 3 — heap_mix(ACC, K[i] XOR a)        |
  |  64 iterations of ARX on heap buffer        |
  +----------------------+-----------------------+
                         |
                         v
  +----------------------------------------------+
  |  STEP 4 — accumulator_round(ACC)            |
  |  a <-- ROTL(a XOR d, 17)                   |
  |  b <-- ROTL(b + a,   23)                   |
  |  c <-- ROTL(c XOR b, 31)                   |
  |  d <-- ROTL(d + c,   47)                   |
  +----------------------+-----------------------+
                         |
                         v  (next key byte, or done)
                   Final ACC[0]

5.4 Key Length and Work Factor

Key length Inner loop iterations ARX rounds
16 bytes 16 × (8 + 64) = 1,152 16
17 bytes 17 × 72 = 1,224 17
32 bytes 32 × 72 = 2,304 32
N bytes N × 72 N

Key initialization cost grows linearly with key length. There is no enforced maximum — arbitrarily long keys are absorbed byte-by-byte.


6. Encryption Process

6.1 Algorithm

void encrypt(uint8_t *data, size_t len, const uint8_t *key, size_t klen) {
    ACC acc;
    init_acc(&acc, key, klen);

    for (size_t i = 0; i < len; i++) {
        uint64_t mix = acc.a ^ acc.b ^ acc.c ^ acc.d;   // 64-bit keystream word
        data[i] ^= (uint8_t)(mix & 0xFF);               // XOR lowest byte

        stack_mix(&acc, data[i]);                        // State update with C[i]
        heap_mix(&acc, mix ^ data[i]);                   // State update with mix XOR C[i]
        accumulator_round(&acc);
    }
}

6.2 Per-Byte Formulas

Keystream generation:

mix[i]  =  a XOR b XOR c XOR d        (full 64-bit word)
ks[i]   =  mix[i]  &  0xFF            (lowest 8 bits)

Encryption:

C[i]  =  M[i] XOR ks[i]

Accumulator update — using the just-produced ciphertext byte C[i]:

ACC  <--  stack_mix( ACC,  C[i] )
ACC  <--  heap_mix( ACC,  mix[i] XOR C[i] )
ACC  <--  accumulator_round( ACC )

6.3 Encryption Flow Diagram

Key  -->  init_acc  -->  ACC[0]
                            |
           +----------------+  for i = 0, 1, ..., N-1
           |                |
           |   mix[i] = a XOR b XOR c XOR d
           |   ks[i]  = mix[i] & 0xFF
           |                |
           |   C[i]  =  M[i] XOR ks[i]     <-- encrypt byte
           |                |
           |   stack_mix(ACC, C[i])
           |   heap_mix(ACC, mix[i] XOR C[i])
           |   accumulator_round(ACC)
           |                |
           +----------------+   ACC[i+1] ready for next byte
                            |
          Output:   C[0] C[1] ... C[N-1]
          Printed:  %02X %02X ... %02X

6.4 Output Encoding

Each ciphertext byte is printed as a two-character uppercase hex pair to safely represent all 256 possible values:

printf("%02X", data[i]);   // Example: 0xAB --> "AB"

7. Decryption Process

7.1 Algorithm

void decrypt(uint8_t *data, size_t len, const uint8_t *key, size_t klen) {
    ACC acc;
    init_acc(&acc, key, klen);

    for (size_t i = 0; i < len; i++) {
        uint64_t mix  = acc.a ^ acc.b ^ acc.c ^ acc.d;
        uint8_t  cipher = data[i];            // Save raw ciphertext byte C[i]
        data[i] ^= (uint8_t)(mix & 0xFF);    // Recover plaintext M[i]

        stack_mix(&acc, cipher);              // State update with C[i] (same as encrypt)
        heap_mix(&acc, mix ^ cipher);
        accumulator_round(&acc);
    }
}

7.2 Per-Byte Formulas

Keystream generation (identical to the encryption path):

mix[i]  =  a XOR b XOR c XOR d
ks[i]   =  mix[i]  &  0xFF

Decryption:

M[i]  =  C[i] XOR ks[i]

Accumulator update — using the saved ciphertext byte C[i]:

ACC  <--  stack_mix( ACC,  C[i] )
ACC  <--  heap_mix( ACC,  mix[i] XOR C[i] )
ACC  <--  accumulator_round( ACC )

7.3 Symmetry Proof

The cipher is symmetric because the state update in both directions consumes the ciphertext byte C[i], not the plaintext. This guarantees that ACC[i+1] is identical on both paths given the same key.

Claim:  ACC_encrypt[i+1]  =  ACC_decrypt[i+1]  for all i

Proof (by induction):
  Base case:    ACC[0] is identical — same key, same init_acc call.
  Inductive step:
    Encrypt feeds  C[i] = M[i] XOR ks[i]  into the state update.
    Decrypt saves C[i] before XOR, feeds the same C[i] into state update.
    Therefore the state transitions are identical --> ACC[i+1] matches.  QED

Correctness of decryption:
  M[i] = C[i] XOR ks[i]
       = (M[i] XOR ks[i]) XOR ks[i]
       = M[i] XOR (ks[i] XOR ks[i])
       = M[i] XOR 0
       = M[i]   (correct)

8. Security Analysis

⚠️ Disclaimer: The analysis below describes design-intent properties based on structural inspection. None of these properties have been formally proven or validated by independent cryptanalysis. MASC-256 is a research cipher; treat all security claims as theoretical until external review confirms them.

8.1 Keystream Entropy

The keystream word at step i is:

mix[i]  =  a[i] XOR b[i] XOR c[i] XOR d[i]
ks[i]   =  mix[i]  &  0xFF

If the four 64-bit words were independently and uniformly distributed, their XOR would also be uniform:

H(ks[i])  =  8 bits   (maximum entropy for a single byte)

The four words are not strictly independent — they share a common update path — but the combination of XOR over GF(2)^64 and addition over Z/2^64 Z makes linear correlation between consecutive keystream bytes computationally hard to exploit.

8.2 Avalanche Effect

A 1-bit change in a key byte or plaintext byte should, after one full state update cycle, produce approximately 50% differing bits in the 256-bit state:

Expected Hamming distance from 1-bit input change:
  ~128 bits out of 256  (approx. 50%)

Propagation trace through accumulator_round alone:

a' = ROTL(a XOR d, 17)     -->  1-bit change in a exits as 1-bit change in a'
b' = ROTL(b + a', 23)      -->  addition carry chain propagates change into b'
c' = ROTL(c XOR b', 31)    -->  change reaches c'
d' = ROTL(d + c', 47)      -->  change reaches d'
Result: all 4 words affected after 1 accumulator_round call.

After the preceding 64 iterations of heap_mix, the change has already spread extensively before accumulator_round executes.

8.3 Resistance to Linear Cryptanalysis

Linear cryptanalysis searches for affine approximations of the form:

L(P) XOR L(C) = 0    with probability p != 1/2

Structural mitigations in MASC-256:

  1. Mixed algebraic groups: XOR operates over GF(2)^64; addition operates over Z/2^64 Z. These groups are algebraically incompatible — a linear approximation that holds over one group breaks down in the other. Every heap_mix iteration combines both operations.

  2. 64 compounding iterations: A linear bias e per iteration compounds to approximately e^64 after 64 rounds — negligibly small for any realistic e.

  3. State feedback: The keystream byte at position i+1 depends on the full state after processing byte i, including the ciphertext byte C[i]. A linear distinguisher must account for the full feedback chain, increasing required data exponentially.

8.4 Resistance to Differential Cryptanalysis

Differential cryptanalysis tracks how a plaintext difference deltaM propagates to a ciphertext difference deltaC.

Key structural property: The accumulator is updated with the ciphertext byte C[i], not the plaintext M[i].

For two plaintexts M and M' differing at position i:

C[i]   = M[i]  XOR ks[i]
C'[i]  = M'[i] XOR ks[i]     (same ks because ACC[i] is the same)

deltaC[i] = C[i] XOR C'[i]  =  M[i] XOR M'[i]  =  deltaM[i]

Both ciphertexts differ at C[i], and these differing bytes drive divergent state updates, meaning ACC[i+1] != ACC'[i+1]. A differential characteristic valid at step i does not extend to step i+1 with high probability, defeating multi-step differential trails.

8.5 Memory-Dependent Side-Channel Properties

The malloc call inside heap_mix introduces runtime variability:

Factor Effect on side-channel attack surface
ASLR randomizes heap base Heap buffer occupies unpredictable cache lines
Allocator free-list state malloc timing varies across calls
512-byte buffer access pattern Cache-line coverage changes per call

A timing side-channel would require an adversary to measure malloc execution time, correlate that to the returned heap address, and infer seed or acc->a. In practice, ASLR and allocator non-determinism make this significantly harder than classical AES S-box cache attacks.

Caveat: No formal cache-timing or power analysis has been performed. These are claimed structural properties only.

8.6 Key Space

For a key of length L bytes drawn from the full 256-symbol alphabet:

|K|  =  256^L  =  2^(8 * L)
Key length Key space cardinality
16 bytes 2^128
32 bytes 2^256 (equal cardinality to AES-256)
64 bytes 2^512

8.7 State Recovery Attack Complexity

To recover the 256-bit state from observed keystream bytes, an adversary must:

  1. Collect keystream bytes ks[0], ks[1], ..., ks[n].
  2. Invert n coupled ARX state transitions (each involving 64 heap rounds + 8 stack rounds + 1 ARX round).
  3. Search a space of 2^256 candidate initial states.

The heap_mix function frees its buffer after each call — the heap addresses used during computation are irrecoverable post-execution. Direct algebraic inversion is infeasible; brute-force complexity remains O(2^256).


9. Test Vectors

9.1 Reference Test Parameters

Key (ASCII):       UltraEncryptedKey
Key (hex):         55 6C 74 72 61 45 6E 63 72 79 70 74 65 64 4B 65 79
Key length:        17 bytes

Plaintext (ASCII): Advanced Memory Encryption System
Plaintext (hex):   41 64 76 61 6E 63 65 64 20 4D 65 6D 6F 72 79 20
                   45 6E 63 72 79 70 74 69 6F 6E 20 53 79 73 74 65 6D
Plaintext length:  33 bytes

9.2 Expected Behavior

$ ./build/Debug/acc_crypto
Encrypted: <66 uppercase hex characters — 33 ciphertext bytes>
Decrypted: Advanced Memory Encryption System

Compile and run main.c to generate the live encrypted hex value. The ciphertext bytes are deterministic within a single process (heap values are computed, not random), but may differ across platforms if compiler optimization changes stack layout. Decryption must always recover the original plaintext.

9.3 Determinism Verification

Run the binary twice and compare outputs:

./build/Debug/acc_crypto > run1.txt
./build/Debug/acc_crypto > run2.txt
diff run1.txt run2.txt    # Must show no differences

9.4 Key Sensitivity Check

A 1-character change to the key must produce a completely different ciphertext. The expected Hamming weight of A XOR B for a 33-byte message:

Expected Hamming distance:   ~132 bits  out of  264 bits   (~50%)
Key Expected result
UltraEncryptedKey Ciphertext A
UltraEncryptedKex Completely different ciphertext B
ultraEncryptedKey Completely different ciphertext C

10. Architecture Diagrams

10.1 Full ACC State Evolution — Per Byte

+------------------------------------------------------------+
|                  ACCUMULATOR  (256 bits)                   |
|  +----------+  +----------+  +----------+  +----------+   |
|  | a (64b)  |  | b (64b)  |  | c (64b)  |  | d (64b)  |   |
|  +----+-----+  +----+-----+  +----+-----+  +----+-----+   |
+-------+-------------+-------------+-------------+---------+
        |             |             |             |
        +-------------+-------------+-------------+
                              |
                      a XOR b XOR c XOR d
                              |
                       & 0xFF = ks[i]
                              |
               M[i]  --XOR-->  C[i]       (encrypt)
               C[i]  --XOR-->  M[i]       (decrypt)
                              |
     +-----------------------+------------------------+
     |              stack_mix(ACC, v)                 |
     |  for i in 0..7:                               |
     |    S[i] = ROTL(v XOR (a+i),  i+1)            |
     |    a XOR= S[i]                                |
     |    b  +=  ROTL(S[i], (i+3) mod 64)           |
     |    c XOR= (b + S[i])                          |
     |    d   =  ROTL(d XOR c, 7)                   |
     +-----------------------+------------------------+
                             |
     +-----------------------+------------------------+
     |              heap_mix(ACC, seed)               |
     |  heap = malloc(64 x 8 bytes)                  |
     |  for i in 0..63:                              |
     |    H[i] = ROTL(seed XOR (a+i), (i%31)+1)     |
     |    a  +=  H[i]                                |
     |    b XOR= ROTL(H[i], (i%17)+3)               |
     |    c  +=  b XOR H[i]                          |
     |    d XOR= ROTL(c, (i%13)+5)                  |
     |  free(heap)                                   |
     +-----------------------+------------------------+
                             |
     +-----------------------+------------------------+
     |          accumulator_round(ACC)                |
     |    a <-- ROTL(a XOR d, 17)                    |
     |    b <-- ROTL(b + a,   23)                    |
     |    c <-- ROTL(c XOR b, 31)                    |
     |    d <-- ROTL(d + c,   47)                    |
     +-----------------------+------------------------+
                             |
                       ACC[i+1]  -->  next byte

10.2 Encrypt vs. Decrypt State Coupling

   ENCRYPT PATH                        DECRYPT PATH
   --------------------                --------------------
   ACC[i]  (same key)                  ACC[i]  (same key)
      |                                   |
      v                                   v
   mix = a XOR b XOR c XOR d          mix = a XOR b XOR c XOR d
   ks  = mix & 0xFF                   ks  = mix & 0xFF
      |                                   |
      v                                   v
   C[i] = M[i] XOR ks          save C[i];  M[i] = C[i] XOR ks
      |                                   |
      v                                   v
   stack_mix(ACC,  C[i])    <-same->   stack_mix(ACC,  C[i])
   heap_mix(ACC,  mix XOR C[i])        heap_mix(ACC,  mix XOR C[i])
   accumulator_round(ACC)              accumulator_round(ACC)
      |                                   |
      v                                   v
   ACC[i+1]          =          ACC[i+1]   <-- guaranteed identical

10.3 Heap Buffer Memory Layout

  malloc(64 x 8 = 512 bytes)
  +--------+--------+--------+-----+----------+-----+----------+
  | heap[0]| heap[1]| heap[2]| ... | heap[31] | ... | heap[63] |
  |  H(0)  |  H(1)  |  H(2)  |     |  H(31)   |     |  H(63)   |
  +--------+--------+--------+-----+----------+-----+----------+
       |                                                  |
       +------------------+-------------------------------+
                          |  for each H(i):
                          |    a  +=   H(i)
                          |    b  XOR= ROTL(H(i), (i%17)+3)
                          |    c  +=   b XOR H(i)
                          |    d  XOR= ROTL(c,    (i%13)+5)
                          v
                     free(heap)  -->  address discarded

11. Comparison with AES-256

Property AES-256 MASC-256
Cipher type Block cipher (128-bit block) Stream cipher (1-byte per step)
Key length Fixed 256 bits Variable (any length)
Internal state 128 bits (4x4 byte matrix) 256 bits (4x64-bit words)
Rounds per block/byte 14 fixed rounds 1 stack (8 iter) + 1 heap (64 iter) + 1 ARX
Inner loop work per byte ~14 x AES round operations 73 inner iterations + 1 ARX round
Parallelizability CTR / GCM modes Inherently sequential
Formal security proof Yes (standard model) None
NIST standardization FIPS 197 None
Side-channel hardening AES-NI constant-time Memory-address variation (partial)
Hardware acceleration AES-NI instruction set None
Public cryptanalysis 25+ years, no key recovery found None
Memory allocations Zero 1 x 512 bytes per plaintext byte
Authentication (AEAD) GCM mode available Not implemented

On the Goal of Surpassing AES-256

AES-256 holds its security reputation because of:

  • 25+ years of open public cryptanalysis with no successful key-recovery attack published.
  • Hardware acceleration (AES-NI) making it among the fastest ciphers on modern CPUs.
  • Formal security reductions and proofs under standard cryptographic assumptions.
  • NIST standardization with audited implementation requirements.

MASC-256's design properties — 256-bit state, memory-dependent entropy, mixed ARX structure, variable-length key absorption — are sound structural choices. However, structural soundness is not the same as proven security. A cipher is not stronger than AES-256 until it survives equivalent public scrutiny over time.

Recommended validation roadmap:

  1. Publish openly (this document is step one).
  2. Submit to the IACR ePrint Archive for community review.
  3. Run the full NIST SP 800-22 statistical test suite on the raw keystream.
  4. Commission a formal cryptanalysis engagement from independent researchers.
  5. Pursue a standardization track if cryptanalysis results are favorable.

12. Known Limitations and Future Work

12.1 Current Limitations

Limitation Description Impact
No IV / Nonce Same key always produces the same keystream Nonce-reuse: XOR two ciphertexts reveals M1 XOR M2
Sequential only Each byte's state depends on all prior bytes Cannot parallelize; no random access within ciphertext
malloc per byte 512-byte allocation per plaintext byte High allocator overhead — far slower than AES on bulk data
No authentication Pure encryption with no integrity check Ciphertext can be tampered undetected
No formal analysis All security properties are unverified claims True security margin is unknown
Deterministic heap values Buffer contents are computable; only addresses vary Side-channel resistance weaker than implied

12.2 Proposed Improvements

  1. Add Nonce / IV — Pass a 128-bit random nonce into init_acc and mix it in before key absorption. Each message then produces a unique keystream even under key reuse.

  2. Implement AEAD mode — Append a 128-bit authentication tag (Poly1305 or GHASH) to detect tampering before decryption.

  3. Batch heap allocation — Allocate the heap buffer once per encrypt/decrypt call and pass it into heap_mix, eliminating per-byte malloc overhead.

  4. Expand keystream extraction — Use all 64 bits of mix to encrypt 8 bytes per state update, increasing throughput 8x without extra state computation.

  5. Statistical testing — Run NIST SP 800-22 and the TestU01 Crush battery on a large keystream sample to verify pseudorandomness properties.

  6. Constant-time implementation — Audit rotation, XOR, and addition paths for data-dependent timing; add mitigations where found.


13. Compilation and Execution

13.1 Prerequisites

  • Compiler: GCC >= 4.8, Clang >= 3.5, or MSVC 2019+ (C99 required)
  • Build system: CMake >= 3.10
  • Standard library: stdlib.h, stdint.h, stddef.h (all standard C)

13.2 Build with CMake

# Configure the build
cmake -B build -S .

# Compile
cmake --build build

# Execute
./build/Debug/acc_crypto

13.3 Build with Make Wrapper

make all    # Configure + compile
make run    # Compile + execute

13.4 Expected Output

Encrypted: <66 uppercase hex characters — 33 bytes>
Decrypted: Advanced Memory Encryption System

13.5 Integrating into Your Own Project

Include acc_crypto.h, compile and link acc_crypto.c and memory_mix.c:

#include "acc_crypto.h"

uint8_t key[]  = "MySecretKey";
uint8_t data[] = "Hello, World!";
size_t  dlen   = sizeof(data) - 1;
size_t  klen   = sizeof(key)  - 1;

// Encrypt in-place
encrypt(data, dlen, key, klen);
// data[] now contains ciphertext bytes

// Decrypt in-place (same key)
decrypt(data, dlen, key, klen);
// data[] restored to "Hello, World!"

14. API Reference

init_acc

void init_acc(ACC *acc, const uint8_t *key, size_t len);

Initializes the 256-bit accumulator acc from key key of byte-length len. Must be called before any encrypt or decrypt operation.


encrypt

void encrypt(uint8_t *data, size_t len, const uint8_t *key, size_t klen);

Encrypts len bytes in data in-place using key. Internally calls init_acc, then processes each byte with stack_mixheap_mixaccumulator_round.


decrypt

void decrypt(uint8_t *data, size_t len, const uint8_t *key, size_t klen);

Decrypts len bytes in data in-place using key. Identical structure to encrypt; saves each ciphertext byte before overwriting to maintain synchronized state.


stack_mix

void stack_mix(ACC *acc, uint64_t v);

Applies 8 rounds of stack-entropy ARX mixing to acc using seed value v.


heap_mix

void heap_mix(ACC *acc, uint64_t seed);

Allocates a 512-byte heap buffer, applies 64 rounds of ARX mixing to acc using seed, then frees the buffer.


accumulator_round

void accumulator_round(ACC *acc);

Applies one 4-word ARX permutation round to acc using rotation constants 17, 23, 31, 47.


rotl / rotr

uint64_t rotl(uint64_t x, int r);
uint64_t rotr(uint64_t x, int r);

64-bit circular left / right rotation of x by r bit positions.