-
Notifications
You must be signed in to change notification settings - Fork 385
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor: keccakf1600 accepts and returns [25]uints.U64 instead of [25]frontend.Variable * feat: add sha3 primitive * fix: sha3 test current hash global elimination => moved to struct
- Loading branch information
1 parent
0896fe1
commit 2a6e749
Showing
6 changed files
with
298 additions
and
26 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 @@ | ||
// Package sha3 provides ZKP circuits for SHA3 hash algorithms applying sponge construction over | ||
// Keccak f-[1600] permutation function. | ||
// | ||
// Instances correspond golang.org/x/crypto/sha3, except SHA224, which is not x64 compatible. | ||
package sha3 |
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 @@ | ||
package sha3 | ||
|
||
import ( | ||
"github.com/consensys/gnark/frontend" | ||
"github.com/consensys/gnark/std/hash" | ||
"github.com/consensys/gnark/std/math/uints" | ||
) | ||
|
||
// New256 creates a new SHA3-256 hash. | ||
// Its generic security strength is 256 bits against preimage attacks, | ||
// and 128 bits against collision attacks. | ||
func New256(api frontend.API) (hash.BinaryHasher, error) { | ||
uapi, err := uints.New[uints.U64](api) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &digest{ | ||
uapi: uapi, | ||
state: newState(), | ||
dsbyte: 0x06, | ||
rate: 136, | ||
outputLen: 32, | ||
}, nil | ||
} | ||
|
||
// New384 creates a new SHA3-384 hash. | ||
// Its generic security strength is 384 bits against preimage attacks, | ||
// and 192 bits against collision attacks. | ||
func New384(api frontend.API) (hash.BinaryHasher, error) { | ||
uapi, err := uints.New[uints.U64](api) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &digest{ | ||
uapi: uapi, | ||
state: newState(), | ||
dsbyte: 0x06, | ||
rate: 104, | ||
outputLen: 48, | ||
}, nil | ||
} | ||
|
||
// New512 creates a new SHA3-512 hash. | ||
// Its generic security strength is 512 bits against preimage attacks, | ||
// and 256 bits against collision attacks. | ||
func New512(api frontend.API) (hash.BinaryHasher, error) { | ||
uapi, err := uints.New[uints.U64](api) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &digest{ | ||
uapi: uapi, | ||
state: newState(), | ||
dsbyte: 0x06, | ||
rate: 72, | ||
outputLen: 64, | ||
}, nil | ||
} | ||
|
||
// NewLegacyKeccak256 creates a new Keccak-256 hash. | ||
// | ||
// Only use this function if you require compatibility with an existing cryptosystem | ||
// that uses non-standard padding. All other users should use New256 instead. | ||
func NewLegacyKeccak256(api frontend.API) (hash.BinaryHasher, error) { | ||
uapi, err := uints.New[uints.U64](api) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &digest{ | ||
uapi: uapi, | ||
state: newState(), | ||
dsbyte: 0x01, | ||
rate: 136, | ||
outputLen: 32, | ||
}, nil | ||
} | ||
|
||
// NewLegacyKeccak512 creates a new Keccak-512 hash. | ||
// | ||
// Only use this function if you require compatibility with an existing cryptosystem | ||
// that uses non-standard padding. All other users should use New512 instead. | ||
func NewLegacyKeccak512(api frontend.API) (hash.BinaryHasher, error) { | ||
uapi, err := uints.New[uints.U64](api) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &digest{ | ||
uapi: uapi, | ||
state: newState(), | ||
dsbyte: 0x01, | ||
rate: 72, | ||
outputLen: 64, | ||
}, nil | ||
} |
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,91 @@ | ||
package sha3 | ||
|
||
import ( | ||
"github.com/consensys/gnark/std/math/uints" | ||
"github.com/consensys/gnark/std/permutation/keccakf" | ||
) | ||
|
||
type digest struct { | ||
uapi *uints.BinaryField[uints.U64] | ||
state [25]uints.U64 // 1600 bits state: 25 x 64 | ||
in []uints.U8 // input to be digested | ||
dsbyte byte // dsbyte contains the "domain separation" bits and the first bit of the padding | ||
rate int // the number of bytes of state to use | ||
outputLen int // the default output size in bytes | ||
} | ||
|
||
func (d *digest) Write(in []uints.U8) { | ||
d.in = append(d.in, in...) | ||
} | ||
|
||
func (d *digest) Size() int { return d.outputLen } | ||
|
||
func (d *digest) Reset() { | ||
d.in = nil | ||
d.state = newState() | ||
} | ||
|
||
func (d *digest) Sum() []uints.U8 { | ||
padded := d.padding() | ||
blocks := d.composeBlocks(padded) | ||
d.absorbing(blocks) | ||
return d.squeezeBlocks() | ||
} | ||
|
||
func (d *digest) padding() []uints.U8 { | ||
padded := make([]uints.U8, len(d.in)) | ||
copy(padded[:], d.in[:]) | ||
|
||
switch q := d.rate - (len(padded) % d.rate); q { | ||
case 1: | ||
padded = append(padded, uints.NewU8(d.dsbyte^0x80)) | ||
case 2: | ||
padded = append(padded, uints.NewU8(d.dsbyte)) | ||
padded = append(padded, uints.NewU8(0x80)) | ||
default: | ||
padded = append(padded, uints.NewU8(d.dsbyte)) | ||
padded = append(padded, uints.NewU8Array(make([]uint8, q-2))...) | ||
padded = append(padded, uints.NewU8(0x80)) | ||
} | ||
|
||
return padded | ||
} | ||
|
||
func (d *digest) composeBlocks(padded []uints.U8) [][]uints.U64 { | ||
blocks := make([][]uints.U64, len(padded)/d.rate) | ||
|
||
for i := range blocks { | ||
block := make([]uints.U64, d.rate/8) | ||
for j := range block { | ||
u64 := padded[j*8 : j*8+8] | ||
block[j] = d.uapi.PackLSB(u64...) | ||
} | ||
blocks[i] = block | ||
padded = padded[d.rate:] | ||
} | ||
|
||
return blocks | ||
} | ||
|
||
func (d *digest) absorbing(blocks [][]uints.U64) { | ||
for _, block := range blocks { | ||
for i := range block { | ||
d.state[i] = d.uapi.Xor(d.state[i], block[i]) | ||
} | ||
d.state = keccakf.Permute(d.uapi, d.state) | ||
} | ||
} | ||
|
||
func (d *digest) squeezeBlocks() (result []uints.U8) { | ||
for i := 0; i < d.outputLen/8; i++ { | ||
result = append(result, d.uapi.UnpackLSB(d.state[i])...) | ||
} | ||
return | ||
} | ||
|
||
func newState() (state [25]uints.U64) { | ||
for i := range state { | ||
state[i] = uints.NewU64(0) | ||
} | ||
return | ||
} |
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,90 @@ | ||
package sha3 | ||
|
||
import ( | ||
"crypto/rand" | ||
"fmt" | ||
"hash" | ||
"testing" | ||
|
||
"github.com/consensys/gnark-crypto/ecc" | ||
"github.com/consensys/gnark/frontend" | ||
zkhash "github.com/consensys/gnark/std/hash" | ||
"github.com/consensys/gnark/std/math/uints" | ||
"github.com/consensys/gnark/test" | ||
"golang.org/x/crypto/sha3" | ||
) | ||
|
||
type testCase struct { | ||
zk func(api frontend.API) (zkhash.BinaryHasher, error) | ||
native func() hash.Hash | ||
} | ||
|
||
var testCases = map[string]testCase{ | ||
"SHA3-256": {New256, sha3.New256}, | ||
"SHA3-384": {New384, sha3.New384}, | ||
"SHA3-512": {New512, sha3.New512}, | ||
"Keccak-256": {NewLegacyKeccak256, sha3.NewLegacyKeccak256}, | ||
"Keccak-512": {NewLegacyKeccak512, sha3.NewLegacyKeccak512}, | ||
} | ||
|
||
type sha3Circuit struct { | ||
In []uints.U8 | ||
Expected []uints.U8 | ||
|
||
hasher string | ||
} | ||
|
||
func (c *sha3Circuit) Define(api frontend.API) error { | ||
newHasher, ok := testCases[c.hasher] | ||
if !ok { | ||
return fmt.Errorf("hash function unknown: %s", c.hasher) | ||
} | ||
h, err := newHasher.zk(api) | ||
if err != nil { | ||
return err | ||
} | ||
uapi, err := uints.New[uints.U64](api) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
h.Write(c.In) | ||
res := h.Sum() | ||
|
||
for i := range c.Expected { | ||
uapi.ByteAssertEq(c.Expected[i], res[i]) | ||
} | ||
return nil | ||
} | ||
|
||
func TestSHA3(t *testing.T) { | ||
assert := test.NewAssert(t) | ||
in := make([]byte, 310) | ||
_, err := rand.Reader.Read(in) | ||
assert.NoError(err) | ||
|
||
for name := range testCases { | ||
assert.Run(func(assert *test.Assert) { | ||
name := name | ||
strategy := testCases[name] | ||
h := strategy.native() | ||
h.Write(in) | ||
expected := h.Sum(nil) | ||
|
||
circuit := &sha3Circuit{ | ||
In: make([]uints.U8, len(in)), | ||
Expected: make([]uints.U8, len(expected)), | ||
hasher: name, | ||
} | ||
|
||
witness := &sha3Circuit{ | ||
In: uints.NewU8Array(in), | ||
Expected: uints.NewU8Array(expected), | ||
} | ||
|
||
if err := test.IsSolved(circuit, witness, ecc.BN254.ScalarField()); err != nil { | ||
t.Fatalf("%s: %s", name, err) | ||
} | ||
}, name) | ||
} | ||
} |
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