NTRU Prime Security-Focused API Documentation

Algorithm: NTRU Prime (sntrup761)
Type: Post-Quantum Key Encapsulation Mechanism
Security Level: ~128-bit quantum, ~200-bit classical
Specification: NTRU Prime Round 3

Security Contract

ntruprime.generateKeypair(): { publicKey: Uint8Array, secretKey: Uint8Array }

Preconditions:

Postconditions:

Side Effects:

ntruprime.encapsulate(publicKey: Uint8Array): { ciphertext: Uint8Array, sharedSecret: Uint8Array }

Preconditions:

Postconditions:

Side Effects:

ntruprime.decapsulate(ciphertext: Uint8Array, secretKey: Uint8Array): Uint8Array

Preconditions:

Postconditions:

Side Effects:

Attack Resistance Matrix

Attack Type Resistance Notes
Quantum (Grover) ✅ 2^128 NIST Level III
Quantum (Shor) ✅ Strong Lattice-based
Decryption Failure ✅ None By design
CCA2 ✅ Proven Fujisaki-Okamoto
Side-Channel ✅ Strong Simple operations
Patent Issues ✅ None Public domain
Backdoors ✅ None Transparent design

Secure Usage Examples

✅ CORRECT: Basic NTRU Prime KEM

import { ntruprime } from 'metamui-ntruprime';

// Generate keypair
const keypair = ntruprime.generateKeypair();

// Encapsulation
const { ciphertext, sharedSecret } = ntruprime.encapsulate(
  keypair.publicKey
);

// Decapsulation
const recovered = ntruprime.decapsulate(
  ciphertext,
  keypair.secretKey
);

// sharedSecret === recovered

// Always use KDF
const sessionKey = kdf.derive(
  sharedSecret,
  "ntruprime-session",
  32
);

✅ CORRECT: Streamlined NTRU Prime

import { ntruprime } from 'metamui-ntruprime';

// Streamlined variant (sntrup761)
class StreamlinedNTRU {
  private keypair: NTRUPrimeKeypair;
  
  constructor() {
    // Streamlined = no decryption failures
    this.keypair = ntruprime.sntrup761.generateKeypair();
  }
  
  // Encapsulate with confirmation
  encapsulateWithConfirmation(
    publicKey: Uint8Array
  ): {
    ciphertext: Uint8Array;
    sharedSecret: Uint8Array;
    confirmation: Uint8Array;
  } {
    const { ciphertext, sharedSecret } = 
      ntruprime.sntrup761.encapsulate(publicKey);
    
    // Key confirmation value
    const confirmation = kdf.derive(
      sharedSecret,
      concat(b"confirm", ciphertext),
      16
    );
    
    return { ciphertext, sharedSecret, confirmation };
  }
}

✅ CORRECT: NTRU Prime + ChaCha20 KEM-DEM

import { ntruprime } from 'metamui-ntruprime';
import { chacha20poly1305 } from 'metamui-chacha20-poly1305';

class NTRUPrimeBox {
  // Efficient public-key encryption
  static seal(
    message: Uint8Array,
    recipientPublicKey: Uint8Array
  ): Uint8Array {
    // KEM
    const { ciphertext, sharedSecret } = 
      ntruprime.encapsulate(recipientPublicKey);
    
    // Derive keys
    const keys = kdf.derive(
      sharedSecret,
      "ntruprime-box-v1",
      64
    );
    
    const encKey = keys.slice(0, 32);
    const authKey = keys.slice(32, 64);
    
    // DEM
    const encrypted = chacha20poly1305.seal(
      message,
      encKey,
      ciphertext.slice(0, 24) // Use part of ciphertext as nonce
    );
    
    return concat(ciphertext, encrypted);
  }
  
  static open(
    sealedBox: Uint8Array,
    secretKey: Uint8Array
  ): Uint8Array {
    // Split KEM and DEM parts
    const ciphertext = sealedBox.slice(0, 1039);
    const encrypted = sealedBox.slice(1039);
    
    // KEM
    const sharedSecret = ntruprime.decapsulate(
      ciphertext,
      secretKey
    );
    
    // Derive keys
    const keys = kdf.derive(
      sharedSecret,
      "ntruprime-box-v1",
      64
    );
    
    const encKey = keys.slice(0, 32);
    
    // DEM
    return chacha20poly1305.open(
      encrypted,
      encKey,
      ciphertext.slice(0, 24)
    );
  }
}

✅ CORRECT: Hybrid with X25519

import { ntruprime } from 'metamui-ntruprime';
import { x25519 } from 'metamui-x25519';

// Belt and suspenders approach
class HybridNTRUX25519 {
  static encapsulate(
    ntruPublicKey: Uint8Array,
    x25519PublicKey: Uint8Array
  ): {
    ciphertext: Uint8Array;
    sharedSecret: Uint8Array;
  } {
    // NTRU Prime KEM
    const ntru = ntruprime.encapsulate(ntruPublicKey);
    
    // X25519 ephemeral
    const ephemeral = x25519.generateKeypair();
    const ecdh = x25519.computeSharedSecret(
      x25519PublicKey,
      ephemeral.secretKey
    );
    
    // Combine ciphertexts
    const ciphertext = concat(
      ntru.ciphertext,        // 1039 bytes
      ephemeral.publicKey     // 32 bytes
    );
    
    // Combine secrets securely
    const sharedSecret = kdf.derive(
      concat(
        new Uint8Array([0x00]), // Domain separator
        ntru.sharedSecret,
        new Uint8Array([0x01]), // Domain separator
        ecdh
      ),
      "hybrid-ntru-x25519",
      32
    );
    
    return { ciphertext, sharedSecret };
  }
}

Common Mistakes to Avoid

❌ WRONG: Using Non-Streamlined Variant

// NEVER DO THIS - Original NTRU has decryption failures
const keypair = ntruprime.ntru761.generateKeypair(); // Not streamlined!

// ✅ CORRECT: Use streamlined variant
const keypair = ntruprime.sntrup761.generateKeypair(); // Streamlined

❌ WRONG: Comparing Ciphertexts

// NEVER DO THIS - Ciphertexts are randomized
const ct1 = ntruprime.encapsulate(publicKey).ciphertext;
const ct2 = ntruprime.encapsulate(publicKey).ciphertext;
if (ct1 === ct2) { // Always false!
  // ...
}

// ✅ CORRECT: Compare shared secrets after decapsulation
const ss1 = ntruprime.decapsulate(ct1, secretKey);
const ss2 = ntruprime.decapsulate(ct2, secretKey);
// ss1 and ss2 are different (randomized KEM)

❌ WRONG: Small Polynomial Attacks

// NEVER DO THIS - Don't try to optimize polynomials
function weakKeyGen() {
  // Trying to use "small" polynomials
  const f = new Int8Array(761).fill(1); // Weak!
  const g = new Int8Array(761).fill(1); // Weak!
  // ...
}

// ✅ CORRECT: Use proper key generation
const keypair = ntruprime.generateKeypair(); // Secure randomness

Implementation Security

Polynomial Multiplication

// Constant-time polynomial multiplication
class NTRUPrimePoly {
  static multiply(
    f: Int8Array,
    g: Int8Array,
    p: number
  ): Int8Array {
    const n = 761;
    const result = new Int8Array(n);
    
    // Schoolbook multiplication (constant-time)
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        const k = (i + j) % n;
        result[k] = modp(
          result[k] + f[i] * g[j],
          p
        );
      }
    }
    
    return result;
  }
  
  // Constant-time modular reduction
  static modp(x: number, p: number): number {
    // Avoid division (timing attack)
    const q = Math.floor(x * INVERSE_P);
    return x - q * p;
  }
}

Sorting Network (Constant-Time)

// Decode uses constant-time sorting
class NTRUPrimeSort {
  static sort32(x: Uint32Array): void {
    const n = x.length;
    
    // Odd-even merge sort network
    for (let p = 1; p < n; p *= 2) {
      for (let k = p; k >= 1; k /= 2) {
        for (let j = k % p; j < n - k; j += 2 * k) {
          for (let i = 0; i < Math.min(k, n - j - k); i++) {
            if ((Math.floor((i + j) / (2 * p)) === 
                 Math.floor((i + j + k) / (2 * p)))) {
              this.compareSwap(x, i + j, i + j + k);
            }
          }
        }
      }
    }
  }
  
  // Constant-time compare and swap
  static compareSwap(x: Uint32Array, i: number, j: number): void {
    const xi = x[i];
    const xj = x[j];
    const b = xi > xj ? 1 : 0;
    x[i] = b ? xj : xi;
    x[j] = b ? xi : xj;
  }
}

Platform-Specific Notes

Performance Characteristics

| Platform | Encapsulation | Decapsulation | |———-|—————|—————| | x86-64 AVX2 | 60 μs | 90 μs | | ARM Cortex-A72 | 150 μs | 200 μs | | WASM (Chrome) | 400 μs | 600 μs |

Memory Requirements

Advantages Over ML-KEM

Feature NTRU Prime ML-KEM
Ciphertext Size 1039 bytes 1088 bytes
Decryption Failures None <2^-164
Design Simplicity Simpler Complex
Patent Status Clear Clear
Standardization Alternative NIST

Security Checklist

Security Analysis

Threat Model: NTRU Prime Threat Model

The comprehensive threat analysis covers:

For complete security analysis and risk assessment, see the dedicated threat model documentation.