arnaucube
arnaucube/keccak256-circomPublic Self-hosted repository
Keccak circuit implemented and it works

Keccak circuit initial version implemented and it works. At this current
commit it only accepts inputs of fixed length nBits, it will be iterated
in future commits.

Currently it needs 150848 constraints.
arnaucube root@arnaucube.com
Thu, 25 Nov 2021 18:33:18 +0100
commit

9522a57cdd370f71dc1b8cad756c14998f2fbcb6

parent

48b66e17f990ee01c08efd788c9d6301694ab439

M circuits/keccak256.circom → circuits/keccak256.circom

@@ -166,3 +166,21 @@ for (i=0; i<25*64; i++) {

out[i] <== round[23].out[i]; } } + +template Keccak(nBits) { + signal input in[nBits]; + signal output out[nBits]; + var i; + + component f = Final(nBits); + for (i=0; i<nBits; i++) { + f.in[i] <== in[i]; + } + component squeeze = Squeeze(nBits); + for (i=0; i<25*64; i++) { + squeeze.s[i] <== f.out[i]; + } + for (i=0; i<nBits; i++) { + out[i] <== squeeze.out[i]; + } +}
M go-keccak256-bits-impl/keccak.go → go-keccak256-bits-impl/keccak.go

@@ -38,6 +38,7 @@ var s [25 * 64]bool

s = absorb(s, last) return s } + func pad(b []bool) []bool { padded := make([]bool, blockSize*8) copy(padded, b)

@@ -46,6 +47,7 @@ copy(padded[(len(padded)-8):],

or(padded[(len(padded)-8):], byteToBits(0x80))) return padded } + func absorb(s [25 * 64]bool, block []bool) [25 * 64]bool { if len(block) != blockSize*8 { panic(fmt.Errorf("absorb: invalid block size: %d, expected: %d",
M go-keccak256-bits-impl/keccak_test.go → go-keccak256-bits-impl/keccak_test.go

@@ -1,7 +1,6 @@

package keccak import ( - "encoding/hex" "fmt" "testing"

@@ -10,19 +9,54 @@ qt "github.com/frankban/quicktest"

) func TestKeccak(t *testing.T) { - testKeccak(t, []byte("test"), "9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658") - testKeccak(t, make([]byte, 32), "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") - testKeccak(t, make([]byte, 100), "913fb9e1f6f1c6d910fd574a5cad8857aa43bfba24e401ada4f56090d4d997a7") + // 32 bytes input + // 1 + testKeccak(t, []byte{116, 101, 115, 116, 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}, + []byte{37, 17, 98, 135, 161, 178, 88, 97, 125, 150, 143, 65, + 228, 211, 170, 133, 153, 9, 88, 212, 4, 212, 175, 238, 249, + 210, 214, 116, 170, 85, 45, 21}) + // 2 + testKeccak(t, []byte{37, 17, 98, 135, 161, 178, 88, 97, 125, 150, 143, + 65, 228, 211, 170, 133, 153, 9, 88, 212, 4, 212, 175, 238, 249, 210, + 214, 116, 170, 85, 45, 21}, + []byte{182, 104, 121, 2, 8, 48, 224, 11, 238, 244, 73, 142, 67, + 205, 166, 27, 10, 223, 142, 209, 10, 46, 171, 110, 239, 68, + 111, 116, 164, 127, 103, 141}) + // 3 + testKeccak(t, []byte{182, 104, 121, 2, 8, 48, 224, 11, 238, 244, 73, + 142, 67, 205, 166, 27, 10, 223, 142, 209, 10, 46, 171, 110, 239, 68, + 111, 116, 164, 127, 103, 141}, + []byte{191, 235, 249, 254, 70, 24, 106, 244, 212, 163, 52, 240, + 1, 128, 235, 61, 158, 52, 138, 60, 197, 80, 113, 36, 44, 217, + 55, 211, 97, 231, 26, 7}) + // 4 + testKeccak(t, make([]byte, 32), []byte{41, 13, 236, 217, 84, 139, 98, + 168, 214, 3, 69, 169, 136, 56, 111, 200, 75, 166, 188, 149, 72, 64, 8, + 246, 54, 47, 147, 22, 14, 243, 229, 99}) + + // variable input length + testKeccak(t, []byte("test"), []byte{156, 34, 255, 95, 33, 240, 184, + 27, 17, 62, 99, 247, 219, 109, 169, 79, 237, 239, 17, 178, 17, 155, 64, + 136, 184, 150, 100, 251, 154, 60, 182, 88}) + // other + testKeccak(t, make([]byte, 100), []byte{145, 63, 185, 225, 246, 241, + 198, 217, 16, 253, 87, 74, 92, 173, 136, 87, 170, 67, 191, 186, 36, + 228, 1, 173, 164, 245, 96, 144, 212, 217, 151, 167}) } -func testKeccak(t *testing.T, input []byte, expectedHex string) { - expected := crypto.Keccak256(input) +func testKeccak(t *testing.T, input, expected []byte) { + // printBytes("in", input, false) + + goH := crypto.Keccak256(input) hBits := ComputeKeccak(bytesToBits(input)) h := bitsToBytes(hBits) + // printBytes("out", goH, false) + + qt.Assert(t, h, qt.DeepEquals, goH) qt.Assert(t, h, qt.DeepEquals, expected) - qt.Assert(t, hex.EncodeToString(h), qt.Equals, expectedHex) } func TestPad(t *testing.T) {

@@ -108,17 +142,25 @@ 3726326332491237029, 7819962668913661099, 2285898735263816116,

13518516210247555620}) } -func printBytes(name string, b []byte) { +func printBytes(name string, b []byte, str bool) { fmt.Printf("%s\n", name) for _, v := range b { - fmt.Printf("\"%v\", ", v) + if str { + fmt.Printf("\"%v\", ", v) + } else { + fmt.Printf("%v, ", v) + } } fmt.Println("") } -func printU64Array(name string, b []uint64) { +func printU64Array(name string, b []uint64, str bool) { fmt.Printf("%s\n", name) for _, v := range b { - fmt.Printf("\"%v\", ", v) + if str { + fmt.Printf("\"%v\", ", v) + } else { + fmt.Printf("%v, ", v) + } } fmt.Println("") }

@@ -132,11 +174,11 @@ 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128} - // printU64Array("s", bitsToU64Array(s[:])) - // printBytes("block", block[:]) + // printU64Array("s", bitsToU64Array(s[:]), true) + // printBytes("block", block[:], true) absorbed := absorb(s, bytesToBits(block)) - // printU64Array("absorbed", bitsToU64Array(absorbed[:])) + // printU64Array("absorbed", bitsToU64Array(absorbed[:]), true) qt.Assert(t, bitsToU64Array(absorbed[:]), qt.DeepEquals, []uint64{8342348566319207042, 319359607942176202, 14410076088654599075,

@@ -150,7 +192,7 @@ 8872504799797239246, 1212062965004664308, 5443427421087086722,

10946808592826700411}) absorbed = absorb(absorbed, bytesToBits(block)) - // printU64Array("absorbed", bitsToU64Array(absorbed[:])) + // printU64Array("absorbed", bitsToU64Array(absorbed[:]), true) qt.Assert(t, bitsToU64Array(absorbed[:]), qt.DeepEquals, []uint64{8909243822027471379, 1111840847970088140,

@@ -173,8 +215,8 @@ bBits := bytesToBits(b)

fBits := final(bBits) - // printBytes("in", b[:]) - // printU64Array("out", bitsToU64Array(fBits[:])) + // printBytes("in", b[:], true) + // printU64Array("out", bitsToU64Array(fBits[:]), true) qt.Assert(t, bitsToU64Array(fBits[:]), qt.DeepEquals, []uint64{16953415415620100490, 7495738965189503699,

@@ -197,8 +239,8 @@ }

bBits = bytesToBits(b) fBits = final(bBits) - // printBytes("in", b[:]) - // printU64Array("out", bitsToU64Array(fBits[:])) + // printBytes("in", b[:], true) + // printU64Array("out", bitsToU64Array(fBits[:]), true) qt.Assert(t, bitsToU64Array(fBits[:]), qt.DeepEquals, []uint64{16852464862333879129, 9588646233186836430, 693207875935078627, 6545910230963382296, 3599194178366828471, 13130606490077331384,

@@ -227,8 +269,8 @@ copy(inBits1600[:], inBits[:])

outBits := squeeze(inBits1600) - // printU64Array("in", in) - // printBytes("out", bitsToBytes(outBits[:])) + // printU64Array("in", in, true) + // printBytes("out", bitsToBytes(outBits[:]), true) qt.Assert(t, bitsToBytes(outBits[:]), qt.DeepEquals, []byte{89, 195, 41, 13, 129, 251, 223, 233, 206, 31, 253, 61,

@@ -250,8 +292,8 @@ copy(inBits1600[:], inBits[:])

outBits = squeeze(inBits1600) - // printU64Array("in", in) - // printBytes("out", bitsToBytes(outBits[:])) + // printU64Array("in", in, true) + // printBytes("out", bitsToBytes(outBits[:]), true) qt.Assert(t, bitsToBytes(outBits[:]), qt.DeepEquals, []byte{138, 225, 170, 89, 127, 161, 70, 235, 211, 170, 44, 237, 223, 54, 6, 104, 222, 165, 229, 38, 86, 126, 146, 176, 50, 24,
A test/circuits/keccak256_test.circom

@@ -0,0 +1,5 @@

+pragma circom 2.0.0; + +include "../../circuits/keccak256.circom"; + +component main = Keccak(32*8);
M test/keccak256.js → test/keccak256.js

@@ -521,3 +521,81 @@ // console.log(stateOutBytes, expectedOut);

assert.deepEqual(stateOutBytes, expectedOut); }); }); + +describe("Keccak full hash test", function () { + this.timeout(100000); + + let cir; + before(async () => { + cir = await c_tester(path.join(__dirname, "circuits", "keccak256_test.circom")); + await cir.loadConstraints(); + console.log("n_constraints", cir.constraints.length); + }); + + it ("Keccak 1 (testvector generated from go)", async () => { + const input = [116, 101, 115, 116, 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]; + const expectedOut = [37, 17, 98, 135, 161, 178, 88, 97, 125, 150, 143, + 65, 228, 211, 170, 133, 153, 9, 88, 212, 4, 212, 175, 238, 249, + 210, 214, 116, 170, 85, 45, 21]; + + const inIn = bytesToBits(input); + + const witness = await cir.calculateWitness({ "in": inIn }, true); + + const stateOut = witness.slice(1, 1+(32*8)); + const stateOutBytes = bitsToBytes(stateOut); + // console.log(stateOutBytes, expectedOut); + assert.deepEqual(stateOutBytes, expectedOut); + }); + it ("Keccak 2 (testvector generated from go)", async () => { + const input = [37, 17, 98, 135, 161, 178, 88, 97, 125, 150, 143, 65, + 228, 211, 170, 133, 153, 9, 88, 212, 4, 212, 175, 238, 249, 210, + 214, 116, 170, 85, 45, 21]; + const expectedOut = [182, 104, 121, 2, 8, 48, 224, 11, 238, 244, 73, + 142, 67, 205, 166, 27, 10, 223, 142, 209, 10, 46, 171, 110, 239, + 68, 111, 116, 164, 127, 103, 141]; + + const inIn = bytesToBits(input); + + const witness = await cir.calculateWitness({ "in": inIn }, true); + + const stateOut = witness.slice(1, 1+(32*8)); + const stateOutBytes = bitsToBytes(stateOut); + // console.log(stateOutBytes, expectedOut); + assert.deepEqual(stateOutBytes, expectedOut); + }); + it ("Keccak 3 (testvector generated from go)", async () => { + const input = [182, 104, 121, 2, 8, 48, 224, 11, 238, 244, 73, 142, 67, + 205, 166, 27, 10, 223, 142, 209, 10, 46, 171, 110, 239, 68, 111, + 116, 164, 127, 103, 141]; + const expectedOut = [191, 235, 249, 254, 70, 24, 106, 244, 212, 163, + 52, 240, 1, 128, 235, 61, 158, 52, 138, 60, 197, 80, 113, 36, 44, + 217, 55, 211, 97, 231, 26, 7]; + + const inIn = bytesToBits(input); + + const witness = await cir.calculateWitness({ "in": inIn }, true); + + const stateOut = witness.slice(1, 1+(32*8)); + const stateOutBytes = bitsToBytes(stateOut); + // console.log(stateOutBytes, expectedOut); + assert.deepEqual(stateOutBytes, expectedOut); + }); + it ("Keccak 4 (testvector generated from go)", async () => { + const input = [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, 0]; + const expectedOut = [41, 13, 236, 217, 84, 139, 98, 168, 214, 3, 69, + 169, 136, 56, 111, 200, 75, 166, 188, 149, 72, 64, 8, 246, 54, 47, + 147, 22, 14, 243, 229, 99]; + + const inIn = bytesToBits(input); + + const witness = await cir.calculateWitness({ "in": inIn }, true); + + const stateOut = witness.slice(1, 1+(32*8)); + const stateOutBytes = bitsToBytes(stateOut); + // console.log(stateOutBytes, expectedOut); + assert.deepEqual(stateOutBytes, expectedOut); + }); +});