Stanford JavaScript Crypto Library providing comprehensive cryptographic operations including AES encryption, hash functions, key derivation, and elliptic curve cryptography.
—
SJCL uses bit arrays (arrays of 32-bit integers) as its internal data representation for all cryptographic operations. The bit array utilities provide essential functions for manipulating, combining, and analyzing these data structures.
Fundamental operations for working with bit arrays.
/**
* Slice bit array in units of bits
* @param {BitArray} a - Source bit array
* @param {number} bstart - Starting bit position
* @param {number} [bend] - Ending bit position (exclusive)
* @returns {BitArray} Sliced bit array
*/
sjcl.bitArray.bitSlice(a, bstart, bend);
/**
* Extract a number from bit array
* @param {BitArray} a - Source bit array
* @param {number} bstart - Starting bit position
* @param {number} blength - Number of bits to extract
* @returns {number} Extracted value as integer
*/
sjcl.bitArray.extract(a, bstart, blength);
/**
* Concatenate two bit arrays
* @param {BitArray} a1 - First bit array
* @param {BitArray} a2 - Second bit array
* @returns {BitArray} Concatenated bit array
*/
sjcl.bitArray.concat(a1, a2);Usage Examples:
const sjcl = require('sjcl');
// Create test bit arrays
const array1 = sjcl.codec.hex.toBits('deadbeef');
const array2 = sjcl.codec.hex.toBits('cafebabe');
console.log('Array 1:', sjcl.codec.hex.fromBits(array1));
console.log('Array 2:', sjcl.codec.hex.fromBits(array2));
// Concatenate arrays
const combined = sjcl.bitArray.concat(array1, array2);
console.log('Combined:', sjcl.codec.hex.fromBits(combined)); // "deadbeefcafebabe"
// Slice bit array
const slice = sjcl.bitArray.bitSlice(combined, 16, 48); // Extract middle 32 bits
console.log('Slice:', sjcl.codec.hex.fromBits(slice));
// Extract specific bits
const extracted = sjcl.bitArray.extract(array1, 8, 8); // Extract bits 8-15
console.log('Extracted byte:', extracted.toString(16));Determine and manipulate bit array lengths.
/**
* Find length of bit array in bits
* @param {BitArray} a - Bit array to measure
* @returns {number} Length in bits
*/
sjcl.bitArray.bitLength(a);
/**
* Truncate bit array to specified bit length
* @param {BitArray} a - Source bit array
* @param {number} len - Desired length in bits
* @returns {BitArray} Truncated bit array
*/
sjcl.bitArray.clamp(a, len);Usage Examples:
const sjcl = require('sjcl');
// Create bit array from hex
const data = sjcl.codec.hex.toBits('deadbeefcafebabe');
console.log('Original length:', sjcl.bitArray.bitLength(data)); // 64 bits
// Clamp to different sizes
const clamped32 = sjcl.bitArray.clamp(data, 32);
console.log('Clamped to 32 bits:', sjcl.codec.hex.fromBits(clamped32)); // "deadbeef"
const clamped24 = sjcl.bitArray.clamp(data, 24);
console.log('Clamped to 24 bits:', sjcl.codec.hex.fromBits(clamped24)); // "deadbe"
// Extend with clamp (pads with zeros)
const extended = sjcl.bitArray.clamp(data, 96);
console.log('Extended to 96 bits:', sjcl.codec.hex.fromBits(extended));
console.log('Extended length:', sjcl.bitArray.bitLength(extended));Handle partial 32-bit words for precise bit manipulation.
/**
* Make partial word for bit array
* @param {number} len - Number of bits used (1-32)
* @param {number} x - Word value
* @param {number} [_end] - Endianness flag
* @returns {number} Partial word with length encoding
*/
sjcl.bitArray.partial(len, x, _end);
/**
* Get number of bits used by partial word
* @param {number} x - Partial word
* @returns {number} Number of bits used (1-32, or 32 if full word)
*/
sjcl.bitArray.getPartial(x);Usage Examples:
const sjcl = require('sjcl');
// Create partial words
const partial8 = sjcl.bitArray.partial(8, 0xAB); // 8-bit partial word
const partial16 = sjcl.bitArray.partial(16, 0xCDEF); // 16-bit partial word
console.log('8-bit partial length:', sjcl.bitArray.getPartial(partial8)); // 8
console.log('16-bit partial length:', sjcl.bitArray.getPartial(partial16)); // 16
// Build array with partial word
const arrayWithPartial = [0xDEADBEEF, partial8];
console.log('Array length:', sjcl.bitArray.bitLength(arrayWithPartial)); // 40 bits
// Convert to hex to see the result
console.log('Hex:', sjcl.codec.hex.fromBits(arrayWithPartial));Secure comparison functions for cryptographic applications.
/**
* Compare two bit arrays for equality in constant time
* @param {BitArray} a - First bit array
* @param {BitArray} b - Second bit array
* @returns {boolean} True if arrays are equal
*/
sjcl.bitArray.equal(a, b);Usage Examples:
const sjcl = require('sjcl');
// Create identical arrays
const array1 = sjcl.codec.hex.toBits('deadbeef');
const array2 = sjcl.codec.hex.toBits('deadbeef');
const array3 = sjcl.codec.hex.toBits('cafebabe');
// Constant-time comparison (secure against timing attacks)
console.log('Arrays 1 and 2 equal:', sjcl.bitArray.equal(array1, array2)); // true
console.log('Arrays 1 and 3 equal:', sjcl.bitArray.equal(array1, array3)); // false
// Use in cryptographic verification
function verifyMAC(message, key, providedMAC) {
const hmac = new sjcl.misc.hmac(key);
const computedMAC = hmac.encrypt(message);
// Secure comparison prevents timing attacks
return sjcl.bitArray.equal(computedMAC, providedMAC);
}
// Example usage
const key = sjcl.random.randomWords(8);
const message = "Important message";
const hmac = new sjcl.misc.hmac(key);
const mac = hmac.encrypt(message);
const isValid = verifyMAC(message, key, mac);
console.log('MAC verification:', isValid);Low-level binary operations on bit arrays.
/**
* XOR two 4-word bit arrays
* @param {BitArray} a - First 4-word array
* @param {BitArray} b - Second 4-word array
* @returns {BitArray} XOR result as 4-word array
*/
sjcl.bitArray.i(a, b);
/**
* Byteswap word array in place
* @param {BitArray} a - Bit array to byteswap
* @returns {BitArray} Same array (modified in place)
*/
sjcl.bitArray.byteswapM(a);Usage Examples:
const sjcl = require('sjcl');
// XOR operation (works on 4-word arrays)
const a = sjcl.codec.hex.toBits('deadbeefcafebabe12345678');
const b = sjcl.codec.hex.toBits('1234567890abcdef87654321');
// Ensure both are exactly 4 words
const a4 = sjcl.bitArray.clamp(a, 128);
const b4 = sjcl.bitArray.clamp(b, 128);
const xorResult = sjcl.bitArray.i(a4, b4);
console.log('XOR result:', sjcl.codec.hex.fromBits(xorResult));
// Byteswap example
const original = sjcl.codec.hex.toBits('deadbeef');
console.log('Original:', sjcl.codec.hex.fromBits(original));
const swapped = sjcl.bitArray.byteswapM(original.slice()); // Clone first
console.log('Byteswapped:', sjcl.codec.hex.fromBits(swapped));Implement bit shifting operations using SJCL's internal functions:
const sjcl = require('sjcl');
// Left shift implementation
function leftShift(bits, positions) {
const result = [];
let carry = 0;
for (let i = bits.length - 1; i >= 0; i--) {
const word = bits[i];
result[i] = ((word << positions) | carry) >>> 0;
carry = word >>> (32 - positions);
}
if (carry !== 0) {
result.unshift(carry);
}
return result;
}
// Right shift implementation
function rightShift(bits, positions) {
const result = [];
let carry = 0;
for (let i = 0; i < bits.length; i++) {
const word = bits[i];
result[i] = (word >>> positions) | carry;
carry = (word << (32 - positions)) >>> 0;
}
return result;
}
// Usage
const data = sjcl.codec.hex.toBits('deadbeef');
console.log('Original:', sjcl.codec.hex.fromBits(data));
const shifted = leftShift(data, 4);
console.log('Left shifted by 4:', sjcl.codec.hex.fromBits(shifted));
const rightShifted = rightShift(data, 4);
console.log('Right shifted by 4:', sjcl.codec.hex.fromBits(rightShifted));Implement custom bit manipulation functions:
const sjcl = require('sjcl');
class BitArrayUtil {
// Set specific bit to 1
static setBit(bits, position) {
const wordIndex = Math.floor(position / 32);
const bitIndex = position % 32;
const result = bits.slice(); // Clone array
if (wordIndex < result.length) {
result[wordIndex] |= (1 << (31 - bitIndex));
}
return result;
}
// Clear specific bit to 0
static clearBit(bits, position) {
const wordIndex = Math.floor(position / 32);
const bitIndex = position % 32;
const result = bits.slice();
if (wordIndex < result.length) {
result[wordIndex] &= ~(1 << (31 - bitIndex));
}
return result;
}
// Test if specific bit is set
static testBit(bits, position) {
const wordIndex = Math.floor(position / 32);
const bitIndex = position % 32;
if (wordIndex >= bits.length) return false;
return (bits[wordIndex] & (1 << (31 - bitIndex))) !== 0;
}
// Count number of set bits (Hamming weight)
static popCount(bits) {
let count = 0;
for (const word of bits) {
// Brian Kernighan's algorithm
let w = word >>> 0; // Ensure unsigned
while (w) {
w &= w - 1;
count++;
}
}
return count;
}
// Reverse bits in array
static reverse(bits) {
const result = [];
const totalBits = sjcl.bitArray.bitLength(bits);
for (let i = 0; i < totalBits; i++) {
const sourceBit = totalBits - 1 - i;
if (BitArrayUtil.testBit(bits, sourceBit)) {
result = BitArrayUtil.setBit(result, i);
} else {
result = BitArrayUtil.clearBit(result, i);
}
}
return sjcl.bitArray.clamp(result, totalBits);
}
}
// Usage examples
const data = sjcl.codec.hex.toBits('f0f0f0f0');
console.log('Original:', sjcl.codec.hex.fromBits(data));
// Set bit 4
const withBitSet = BitArrayUtil.setBit(data, 4);
console.log('Bit 4 set:', sjcl.codec.hex.fromBits(withBitSet));
// Test bits
console.log('Bit 0 set:', BitArrayUtil.testBit(data, 0)); // true (f starts with 1111)
console.log('Bit 4 set:', BitArrayUtil.testBit(data, 4)); // false (0 in f0f0)
// Count set bits
console.log('Set bits:', BitArrayUtil.popCount(data));Validate bit array integrity and format:
const sjcl = require('sjcl');
class BitArrayValidator {
// Check if array is valid bit array
static isValid(bits) {
if (!Array.isArray(bits)) return false;
for (let i = 0; i < bits.length; i++) {
const word = bits[i];
// Check if it's a number
if (typeof word !== 'number') return false;
// Check if it's a valid 32-bit integer
if (!Number.isInteger(word)) return false;
// Check if it's in valid range
if (word < 0 || word > 0xFFFFFFFF) return false;
// Check partial word (last word might have length encoding)
if (i === bits.length - 1) {
const partialBits = sjcl.bitArray.getPartial(word);
if (partialBits < 1 || partialBits > 32) return false;
}
}
return true;
}
// Sanitize bit array
static sanitize(bits) {
if (!Array.isArray(bits)) return [];
return bits.filter(word => {
return typeof word === 'number' &&
Number.isInteger(word) &&
word >= 0 &&
word <= 0xFFFFFFFF;
});
}
// Get array statistics
static getStats(bits) {
return {
isValid: BitArrayValidator.isValid(bits),
wordCount: bits.length,
bitLength: sjcl.bitArray.bitLength(bits),
byteLength: Math.ceil(sjcl.bitArray.bitLength(bits) / 8),
isEmpty: bits.length === 0,
hasPartialWord: bits.length > 0 && sjcl.bitArray.getPartial(bits[bits.length - 1]) !== 32
};
}
}
// Usage
const testData = sjcl.codec.hex.toBits('deadbeef');
const stats = BitArrayValidator.getStats(testData);
console.log('Array stats:', stats);
// Test invalid data
const invalidData = [0xDEADBEEF, -1, 'invalid', 0x100000000];
console.log('Invalid data valid:', BitArrayValidator.isValid(invalidData)); // false
const sanitized = BitArrayValidator.sanitize(invalidData);
console.log('Sanitized:', sanitized); // [0xDEADBEEF].slice() to clone arrays before modificationsjcl.bitArray.equal() for secure comparisonsclamp() to pad or truncate databitSlice() to process data in chunksequal() for cryptographic comparisonsconcat() to build composite data structuresInstall with Tessl CLI
npx tessl i tessl/npm-sjcl