Tidy and improve consistency
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Jack Hadrill 2022-01-30 00:36:58 +00:00
parent f378a93bae
commit 4241d4545c

View File

@ -186,106 +186,125 @@ function calculateDomainSeparation (combinedData: number[][], parsedMessageLengt
* @param associatedData The associated data to encrypt. * @param associatedData The associated data to encrypt.
* @param nonce A 128 bit nonce. * @param nonce A 128 bit nonce.
* @param key A 128 bit encryption key. * @param key A 128 bit encryption key.
* @returns The ciphertext. * @returns The encrypted ciphertext.
*/ */
export function cryptoAeadEncrypt (message: number[], associatedData: number[], nonce: number[], key: number[]): number[] { export function cryptoAeadEncrypt (message: number[], associatedData: number[], nonce: number[], key: number[]): number[] {
// Buffer for ciphertext.
const ciphertext = []
// Reset state and counter.
let state = zeroedBuffer(16)
let counter = resetCounter() let counter = resetCounter()
// Carve message and associated data into blocks. // Carve message and associated data into blocks.
const parsedMessage = parse(message, 16) const messageBlocks = parse(message, 16)
const parsedMessageLength = parsedMessage.length - 1 const messageBlockCount = messageBlocks.length - 1
const parsedAssociatedData = parse(associatedData, 16) const associatedDataBlocks = parse(associatedData, 16)
const parsedAssociatedDataLength = parsedAssociatedData.length - 1 const associatedDataBlockCount = associatedDataBlocks.length - 1
// Concatenate the parsed message and the associated data, excluding each array's first element. // Concatenate the message and associated data blocks, excluding each array's first element.
const combinedData = parsedAssociatedData.slice(1).concat(parsedMessage.slice(1)) const combinedDataBlocks = associatedDataBlocks.slice(1).concat(messageBlocks.slice(1))
// Insert empty array at position 0. // Insert empty array at position 0.
combinedData.splice(0, 0, []) combinedDataBlocks.splice(0, 0, [])
// Calculate domain separation for final encryption stage. // Calculate domain separation for final encryption stage.
const domainSeparation = calculateDomainSeparation(combinedData, parsedMessageLength, parsedAssociatedDataLength) const domainSeparation = calculateDomainSeparation(combinedDataBlocks, messageBlockCount, associatedDataBlockCount)
// Pad combined data. // Pad combined data.
combinedData[parsedAssociatedDataLength] = pad(combinedData[parsedAssociatedDataLength], 16) combinedDataBlocks[associatedDataBlockCount] = pad(combinedDataBlocks[associatedDataBlockCount], 16)
combinedData[parsedAssociatedDataLength + parsedMessageLength] = pad(combinedData[parsedAssociatedDataLength + parsedMessageLength], 16) combinedDataBlocks[associatedDataBlockCount + messageBlockCount] = pad(combinedDataBlocks[associatedDataBlockCount + messageBlockCount], 16)
// Do the encryption. // Do the encryption.
// See https://romulusae.github.io/romulus/docs/Romulusv1.3.pdf for more information. // See https://romulusae.github.io/romulus/docs/Romulusv1.3.pdf for more information.
let state = zeroedBuffer(16)
let c = zeroedBuffer(16)
let x = 8 let x = 8
for (let i = 1; i < Math.floor((parsedAssociatedDataLength + parsedMessageLength) / 2) + 1; i++) {
[state, c] = rho(state, combinedData[2 * i - 1]) for (let i = 1; i < Math.floor((associatedDataBlockCount + messageBlockCount) / 2) + 1; i++) {
[state] = rho(state, combinedDataBlocks[2 * i - 1])
counter = increaseCounter(counter) counter = increaseCounter(counter)
if (i === Math.floor(parsedAssociatedDataLength / 2) + 1) { if (i === Math.floor(associatedDataBlockCount / 2) + 1) {
x = x ^ 4 x = x ^ 4
} }
state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedData[2 * i], key)) state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedDataBlocks[2 * i], key))
counter = increaseCounter(counter) counter = increaseCounter(counter)
} }
if (parsedAssociatedDataLength % 2 === parsedMessageLength % 2) { if (associatedDataBlockCount % 2 === messageBlockCount % 2) {
[state, c] = rho(state, zeroedBuffer(16)) [state] = rho(state, zeroedBuffer(16))
} else { } else {
[state, c] = rho(state, combinedData[parsedAssociatedDataLength + parsedMessageLength]) [state] = rho(state, combinedDataBlocks[associatedDataBlockCount + messageBlockCount])
counter = increaseCounter(counter) counter = increaseCounter(counter)
} }
[state, c] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), zeroedBuffer(16)) const [,authenticationTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), zeroedBuffer(16))
if (message.length === 0) { if (message.length === 0) {
return c return authenticationTag
} }
state = [...c] state = [...authenticationTag]
const ciphertext = []
const originalLastBlockLength = parsedMessage[parsedMessageLength].length
parsedMessage[parsedMessageLength] = pad(parsedMessage[parsedMessageLength], 16)
counter = resetCounter() counter = resetCounter()
for (let i = 1; i < parsedMessageLength + 1; i++) {
const originalFinalMessageBlockLength = messageBlocks[messageBlockCount].length
messageBlocks[messageBlockCount] = pad(messageBlocks[messageBlockCount], 16)
for (let i = 1; i < messageBlockCount + 1; i++) {
state = skinnyEncrypt(state, tweakeyEncode(counter, 4, nonce, key)) state = skinnyEncrypt(state, tweakeyEncode(counter, 4, nonce, key))
let x
[state, x] = rho(state, parsedMessage[i]) let cBlock
[state, cBlock] = rho(state, messageBlocks[i])
counter = increaseCounter(counter) counter = increaseCounter(counter)
if (i < parsedMessageLength) {
ciphertext.push(...x) if (i < messageBlockCount) {
ciphertext.push(...cBlock)
} else { } else {
ciphertext.push(...x.slice(0, originalLastBlockLength)) ciphertext.push(...cBlock.slice(0, originalFinalMessageBlockLength))
} }
} }
ciphertext.push(...c) // The authentication tag is stored in the final 16 bytes of the ciphertext.
ciphertext.push(...authenticationTag)
return ciphertext return ciphertext
} }
/**
* Decrypt a message using the Romulus-M cryptography specification.
* @param ciphertext The ciphertext to decrypt.
* @param associatedData The associated data.
* @param nonce The nonce.
* @param key The key.
* @returns The decrypted plaintext.
*/
export function cryptoAeadDecrypt (ciphertext: number[], associatedData: number[], nonce: number[], key: number[]): number[] { export function cryptoAeadDecrypt (ciphertext: number[], associatedData: number[], nonce: number[], key: number[]): number[] {
// Buffer for decrypted message. // Buffer for decrypted message.
const message = [] const message = []
// The authentication tag is represented by the last 16 bytes of the ciphertext. // The authentication tag is represented by the final 16 bytes of the ciphertext.
const authenticationTag = ciphertext.slice(-16) const authenticationTag = ciphertext.slice(-16)
ciphertext.length -= 16 ciphertext.length -= 16
// Reset state and counter.
let state = zeroedBuffer(16) let state = zeroedBuffer(16)
let counter = resetCounter() let counter = resetCounter()
if (ciphertext.length !== 0) { if (ciphertext.length !== 0) {
// Combine the ciphertext
state = [...authenticationTag] state = [...authenticationTag]
const parsedCiphertext = parse(ciphertext, 16) const ciphertextBlocks = parse(ciphertext, 16)
const parsedCiphertextLength = parsedCiphertext.length - 1 const ciphertextBlockCount = ciphertextBlocks.length - 1
const finalCiphertextBlockLength = parsedCiphertext[parsedCiphertextLength].length const finalCiphertextBlockLength = ciphertextBlocks[ciphertextBlockCount].length
parsedCiphertext[parsedCiphertextLength] = pad(parsedCiphertext[parsedCiphertextLength], 16) ciphertextBlocks[ciphertextBlockCount] = pad(ciphertextBlocks[ciphertextBlockCount], 16)
for (let i = 1; i < parsedCiphertextLength + 1; i++) { for (let i = 1; i < ciphertextBlockCount + 1; i++) {
state = skinnyEncrypt(state, tweakeyEncode(counter, 4, nonce, key)) state = skinnyEncrypt(state, tweakeyEncode(counter, 4, nonce, key))
let mBlock let mBlock
[state, mBlock] = inverseRoh(state, parsedCiphertext[i]) [state, mBlock] = inverseRoh(state, ciphertextBlocks[i])
counter = increaseCounter(counter) counter = increaseCounter(counter)
if (i < parsedCiphertextLength) {
if (i < ciphertextBlockCount) {
message.push(...mBlock) message.push(...mBlock)
} else { } else {
message.push(...mBlock.slice(0, finalCiphertextBlockLength)) message.push(...mBlock.slice(0, finalCiphertextBlockLength))
@ -295,47 +314,50 @@ export function cryptoAeadDecrypt (ciphertext: number[], associatedData: number[
state = [] state = []
} }
// Reset state and counter.
state = zeroedBuffer(16) state = zeroedBuffer(16)
counter = resetCounter() counter = resetCounter()
const parsedAssociatedData = parse(associatedData, 16) // Carve the message and associated data into blocks.
const parsedAssociatedDataLength = parsedAssociatedData.length - 1 const messageBlocks = parse(message, 16)
const messageBlockLength = messageBlocks.length - 1
const parsedMessage = parse(message, 16) const associatedDataBlocks = parse(associatedData, 16)
const parsedMessageLength = parsedMessage.length - 1 const associatedDataBlockCount = associatedDataBlocks.length - 1
// Concatenate the parsed message and the associated data, excluding each array's first element. // Concatenate the message and associated data blocks, excluding each array's first element.
const combinedData = parsedAssociatedData.slice(1).concat(parsedMessage.slice(1)) const combinedData = associatedDataBlocks.slice(1).concat(messageBlocks.slice(1))
// Insert empty array at position 0. // Insert empty array at position 0.
combinedData.splice(0, 0, []) combinedData.splice(0, 0, [])
// Calculate domain separation for final decryption stage. // Calculate domain separation for final decryption stage.
const domainSeparation = calculateDomainSeparation(combinedData, parsedMessageLength, parsedAssociatedDataLength) const domainSeparation = calculateDomainSeparation(combinedData, messageBlockLength, associatedDataBlockCount)
// Pad combined data. // Pad combined data.
combinedData[parsedAssociatedDataLength] = pad(combinedData[parsedAssociatedDataLength], 16) combinedData[associatedDataBlockCount] = pad(combinedData[associatedDataBlockCount], 16)
combinedData[parsedAssociatedDataLength + parsedMessageLength] = pad(combinedData[parsedAssociatedDataLength + parsedMessageLength], 16) combinedData[associatedDataBlockCount + messageBlockLength] = pad(combinedData[associatedDataBlockCount + messageBlockLength], 16)
let x = 8 let x = 8
for (let i = 1; i < Math.floor((parsedAssociatedDataLength + parsedMessageLength) / 2) + 1; i++) { for (let i = 1; i < Math.floor((associatedDataBlockCount + messageBlockLength) / 2) + 1; i++) {
[state] = rho(state, combinedData[2 * i - 1]) [state] = rho(state, combinedData[2 * i - 1])
counter = increaseCounter(counter) counter = increaseCounter(counter)
if (i === Math.floor(parsedAssociatedDataLength / 2) + 1) { if (i === Math.floor(associatedDataBlockCount / 2) + 1) {
x = x ^ 4 x = x ^ 4
} }
state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedData[2 * i], key)) state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedData[2 * i], key))
counter = increaseCounter(counter) counter = increaseCounter(counter)
} }
if (parsedAssociatedDataLength % 2 === parsedMessageLength % 2) { if (associatedDataBlockCount % 2 === messageBlockLength % 2) {
[state] = rho(state, zeroedBuffer(16)) [state] = rho(state, zeroedBuffer(16))
} else { } else {
[state] = rho(state, combinedData[parsedAssociatedDataLength + parsedMessageLength]) [state] = rho(state, combinedData[associatedDataBlockCount + messageBlockLength])
counter = increaseCounter(counter) counter = increaseCounter(counter)
} }
let computedTag
[state, computedTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), zeroedBuffer(16)) // Calculate authentication tag.
const [,computedTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), zeroedBuffer(16))
let compare = 0 let compare = 0
for (let i = 0; i < 16; i++) { for (let i = 0; i < 16; i++) {