-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
332 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pragma circom 2.1.9; | ||
|
||
include "../../circuits/chacha20/chacha20.circom"; | ||
|
||
component main = ChaCha20_NIVC(256); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pragma circom 2.1.9; | ||
|
||
include "../../circuits/chacha20/nivc/chacha20_nivc.circom"; | ||
|
||
component main = ChaCha20_NIVC(128); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// initially from https://github.com/reclaimprotocol/zk-symmetric-crypto | ||
// modified for our needs | ||
pragma circom 2.1.9; | ||
|
||
include "../chacha-round.circom"; | ||
include "../chacha-qr.circom"; | ||
include "../../utils/generics-bits.circom"; | ||
include "../../utils/hash.circom"; | ||
include "../../utils/array.circom"; | ||
|
||
|
||
/** ChaCha20 in counter mode */ | ||
// Chacha20 opperates a 4x4 matrix of 32-bit words where the first 4 words are constants: C | ||
// and the next 8 words are the 256 bit key: K. The next 2 words are the block counter: # | ||
// and the last 2 words are the nonce: N. | ||
// +---+---+---+---+ | ||
// | C | C | C | C | | ||
// +---+---+---+---+ | ||
// | K | K | K | K | | ||
// +---+---+---+---+ | ||
// | K | K | K | K | | ||
// +---+---+---+---+ | ||
// | # | N | N | N | | ||
// +---+---+---+---+ | ||
// paramaterized by n which is the number of 32-bit words to encrypt | ||
template ChaCha20_NIVC(N) { | ||
// key => 8 32-bit words = 32 bytes | ||
signal input key[8][32]; | ||
// nonce => 3 32-bit words = 12 bytes | ||
signal input nonce[3][32]; | ||
// counter => 32-bit word to apply w nonce | ||
signal input counter[32]; | ||
|
||
// the below can be both ciphertext or plaintext depending on the direction | ||
// in => N 32-bit words => N 4 byte words | ||
signal input plainText[N][32]; | ||
// out => N 32-bit words => N 4 byte words | ||
signal input cipherText[N][32]; | ||
|
||
signal input step_in[1]; | ||
signal output step_out[1]; | ||
|
||
var tmp[16][32] = [ | ||
[ | ||
// constant 0x61707865 | ||
0, 1, 1, 0, 0, 0, 0, 1, 0, | ||
1, 1, 1, 0, 0, 0, 0, 0, 1, | ||
1, 1, 1, 0, 0, 0, 0, 1, 1, | ||
0, 0, 1, 0, 1 | ||
], | ||
[ | ||
// constant 0x3320646e | ||
0, 0, 1, 1, 0, 0, 1, 1, 0, | ||
0, 1, 0, 0, 0, 0, 0, 0, 1, | ||
1, 0, 0, 1, 0, 0, 0, 1, 1, | ||
0, 1, 1, 1, 0 | ||
], | ||
[ | ||
// constant 0x79622d32 | ||
0, 1, 1, 1, 1, 0, 0, 1, 0, | ||
1, 1, 0, 0, 0, 1, 0, 0, 0, | ||
1, 0, 1, 1, 0, 1, 0, 0, 1, | ||
1, 0, 0, 1, 0 | ||
], | ||
[ | ||
// constant 0x6b206574 | ||
0, 1, 1, 0, 1, 0, 1, 1, 0, | ||
0, 1, 0, 0, 0, 0, 0, 0, 1, | ||
1, 0, 0, 1, 0, 1, 0, 1, 1, | ||
1, 0, 1, 0, 0 | ||
], | ||
key[0], key[1], key[2], key[3], | ||
key[4], key[5], key[6], key[7], | ||
counter, nonce[0], nonce[1], nonce[2] | ||
]; | ||
|
||
// 1 in 32-bit words | ||
signal one[32]; | ||
one <== [ | ||
0, 0, 0, 0, 0, 0, 0, 0, | ||
0, 0, 0, 0, 0, 0, 0, 0, | ||
0, 0, 0, 0, 0, 0, 0, 0, | ||
0, 0, 0, 0, 0, 0, 0, 1 | ||
]; | ||
|
||
var i = 0; | ||
var j = 0; | ||
|
||
// do the ChaCha20 rounds | ||
// rounds opperates on 4 words at a time | ||
component rounds[N/16]; | ||
component xors[N]; | ||
component counter_adder[N/16 - 1]; | ||
|
||
signal computedCipherText[N][32]; | ||
|
||
for(i = 0; i < N/16; i++) { | ||
rounds[i] = Round(); | ||
rounds[i].in <== tmp; | ||
// XOR block with input | ||
for(j = 0; j < 16; j++) { | ||
xors[i*16 + j] = XorBits(32); | ||
xors[i*16 + j].a <== plainText[i*16 + j]; | ||
xors[i*16 + j].b <== rounds[i].out[j]; | ||
computedCipherText[i*16 + j] <== xors[i*16 + j].out; | ||
} | ||
|
||
if(i < N/16 - 1) { | ||
counter_adder[i] = AddBits(32); | ||
counter_adder[i].a <== tmp[12]; | ||
counter_adder[i].b <== one; | ||
|
||
// increment the counter | ||
tmp[12] = counter_adder[i].out; | ||
} | ||
} | ||
|
||
signal ciphertext_equal_check[N][32]; | ||
for(var i = 0 ; i < N; i++) { | ||
for(var j = 0 ; j < 32 ; j++) { | ||
ciphertext_equal_check[i][j] <== IsEqual()([computedCipherText[i][j], cipherText[i][j]]); | ||
ciphertext_equal_check[i][j] === 1; | ||
} | ||
} | ||
|
||
var packedPlaintext[N]; // Each element will be a 32-bit word | ||
for(var i = 0; i < N; i++) { | ||
packedPlaintext[i] = 0; | ||
for(var j = 0; j < 32; j++) { // Loop through all 32 bits | ||
packedPlaintext[i] += plainText[i][j] * 2**j; // Now we shift by single bits | ||
} | ||
} | ||
|
||
signal hash[N]; | ||
hash[0] <== PoseidonChainer()([step_in[0], packedPlaintext[0]]); | ||
for(var i = 1 ; i < N ; i++) { | ||
hash[i] <== PoseidonChainer()([hash[i-1], packedPlaintext[i]]); | ||
} | ||
step_out[0] <== hash[N-1]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { WitnessTester } from "circomkit"; | ||
import { circomkit, bytesToBigInt, toUint32Array, uintArray32ToBits, bitsToBytes } from "../common"; | ||
import { PoseidonModular } from "../common/poseidon"; | ||
import { assert } from "chai"; | ||
|
||
|
||
describe("chacha20-nivc", () => { | ||
// this is failing right now | ||
describe("2 block test", () => { | ||
let circuit: WitnessTester<["key", "nonce", "counter", "plainText", "cipherText", "step_in"], ["step_out"]>; | ||
it("should perform encryption", async () => { | ||
circuit = await circomkit.WitnessTester(`ChaCha20`, { | ||
file: "chacha20/nivc/chacha20_nivc", | ||
template: "ChaCha20_NIVC", | ||
params: [16] // number of 32-bit words in the key, 512 / 32 = 16 | ||
}); | ||
// Test case from RCF https://www.rfc-editor.org/rfc/rfc7539.html#section-2.4.2 | ||
// the input encoding here is not the most intuitive. inputs are serialized as little endian. | ||
// i.e. "e4e7f110" is serialized as "10 f1 e7 e4". So the way i am reading in inputs is | ||
// to ensure that every 32 bit word is byte reversed before being turned into bits. | ||
// i think this should be easy when we compute witness in rust. | ||
let test = { | ||
keyBytes: Buffer.from( | ||
[ | ||
0x00, 0x01, 0x02, 0x03, | ||
0x04, 0x05, 0x06, 0x07, | ||
0x08, 0x09, 0x0a, 0x0b, | ||
0x0c, 0x0d, 0x0e, 0x0f, | ||
0x10, 0x11, 0x12, 0x13, | ||
0x14, 0x15, 0x16, 0x17, | ||
0x18, 0x19, 0x1a, 0x1b, | ||
0x1c, 0x1d, 0x1e, 0x1f | ||
] | ||
), | ||
nonceBytes: Buffer.from( | ||
[ | ||
0x00, 0x00, 0x00, 0x00, | ||
0x00, 0x00, 0x00, 0x4a, | ||
0x00, 0x00, 0x00, 0x00 | ||
] | ||
), | ||
counter: 1, | ||
plaintextBytes: Buffer.from( | ||
[ | ||
0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, | ||
0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, | ||
0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, | ||
0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, | ||
] | ||
), | ||
ciphertextBytes: Buffer.from( | ||
[ | ||
0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81, | ||
0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, | ||
0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57, | ||
0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab, 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8 | ||
] | ||
)} | ||
const ciphertextBits = uintArray32ToBits(toUint32Array(test.ciphertextBytes)) | ||
const plaintextBits = uintArray32ToBits(toUint32Array(test.plaintextBytes)) | ||
const counterBits = uintArray32ToBits([test.counter])[0] | ||
let w = await circuit.compute({ | ||
key: uintArray32ToBits(toUint32Array(test.keyBytes)), | ||
nonce: uintArray32ToBits(toUint32Array(test.nonceBytes)), | ||
counter: counterBits, | ||
cipherText: ciphertextBits, | ||
plainText: plaintextBits, | ||
step_in: 0 | ||
}, (["step_out"])); | ||
assert.deepEqual(w.step_out, testing(uintArray32ToBits(toUint32Array(test.plaintextBytes)))); | ||
}); | ||
}); | ||
}); | ||
|
||
|
||
function testing(Bytes: number[][]): bigint { | ||
let hashes: bigint[] = [BigInt(0)]; // Initialize first hash as 0 | ||
|
||
for (let i = 0; i < Bytes.length; i++) { | ||
let packedInput = BigInt(0); | ||
for (let j = 0; j < 32; j++) { | ||
packedInput += BigInt(Bytes[i][j]) * BigInt(Math.pow(2, j)); | ||
} | ||
// Compute next hash using previous hash and packed input, but if packed input is zero, don't hash it. | ||
if (packedInput == BigInt(0)) { | ||
hashes.push(hashes[i]); | ||
} else { | ||
let hash = PoseidonModular([hashes[i], packedInput]); | ||
hashes.push(hash); | ||
} | ||
} | ||
// Return the last hash | ||
return hashes[Bytes.length]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// initially from https://github.com/reclaimprotocol/zk-symmetric-crypto | ||
// modified for our needs | ||
pragma circom 2.1.9; | ||
|
||
/** | ||
* Add N bit numbers together | ||
* copied in from: https://github.com/iden3/circomlib/blob/master/circuits/binsum.circom | ||
* but rewritten slightly to reduce the final number of wires & labels | ||
* and possibly look at reducing the number of constraints | ||
*/ | ||
template AddBits(BITS) { | ||
signal input a[BITS]; | ||
signal input b[BITS]; | ||
signal output out[BITS]; | ||
signal carrybit; | ||
|
||
var lin = 0; | ||
var lout = 0; | ||
|
||
var k; | ||
var j = 0; | ||
|
||
var e2; | ||
|
||
// create e2 which | ||
// is the numerical sum of 2^k | ||
e2 = 1; | ||
for (k = BITS - 1; k >= 0; k--) { | ||
lin += (a[k] + b[k]) * e2; | ||
e2 *= 2; | ||
} | ||
|
||
e2 = 1; | ||
for (k = BITS - 1; k >= 0; k--) { | ||
out[k] <-- (lin >> j) & 1; | ||
// Ensure out is binary | ||
out[k] * (out[k] - 1) === 0; | ||
lout += out[k] * e2; | ||
e2 *= 2; | ||
j += 1; | ||
} | ||
|
||
carrybit <-- (lin >> j) & 1; | ||
// Ensure out is binary | ||
carrybit * (carrybit - 1) === 0; | ||
lout += carrybit * e2; | ||
|
||
// Ensure the sum matches | ||
lin === lout; | ||
} | ||
|
||
/** | ||
* Rotate left a BITS bit integer L bits | ||
*/ | ||
template RotateLeftBits(BITS, L) { | ||
signal input in[BITS]; | ||
signal output out[BITS]; | ||
for (var i = 0; i < BITS; i++) { | ||
out[i] <== in[(i + L) % BITS]; | ||
} | ||
} | ||
|
||
/** | ||
* XOR BITS-bit words | ||
*/ | ||
template XorBits(BITS) { | ||
signal input a[BITS]; | ||
signal input b[BITS]; | ||
signal output out[BITS]; | ||
|
||
for (var k=0; k<BITS; k++) { | ||
out[k] <== a[k] + b[k] - 2*a[k]*b[k]; | ||
} | ||
} |