diff --git a/std/hash/ripemd160/ripemd160.go b/std/hash/ripemd160/ripemd160.go new file mode 100644 index 000000000..2195067ff --- /dev/null +++ b/std/hash/ripemd160/ripemd160.go @@ -0,0 +1,82 @@ +// Package ripemd160 implements in-circuit ripemd160 hash function. +// +// This package extends the permutation function [ripemd160.Permute] into a full +// hash function with padding computation and [hash.BinaryHasher] interface +// implementation. +package ripemd160 + +import ( + "encoding/binary" + "fmt" + + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/hash" + "github.com/consensys/gnark/std/math/uints" + "github.com/consensys/gnark/std/permutation/ripemd160" +) + +var _seed = uints.NewU32Array([]uint32{ + 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0, +}) + +type digest struct { + uapi *uints.BinaryField[uints.U32] + in []uints.U8 +} + +// New returns a new ripemd160 hasher. +func New(api frontend.API) (hash.BinaryHasher, error) { + uapi, err := uints.New[uints.U32](api) + if err != nil { + return nil, fmt.Errorf("new uapi: %w", err) + } + return &digest{uapi: uapi}, nil +} + +func (d *digest) Write(data []uints.U8) { + d.in = append(d.in, data...) +} + +func (d *digest) padded(bytesLen int) []uints.U8 { + zeroPadLen := 55 - bytesLen%64 + if zeroPadLen < 0 { + zeroPadLen += 64 + } + if cap(d.in) < len(d.in)+9+zeroPadLen { + // in case this is the first time this method is called increase the + // capacity of the slice to fit the padding. + d.in = append(d.in, make([]uints.U8, 9+zeroPadLen)...) + d.in = d.in[:len(d.in)-9-zeroPadLen] + } + buf := d.in + buf = append(buf, uints.NewU8(0x80)) + buf = append(buf, uints.NewU8Array(make([]uint8, zeroPadLen))...) + lenbuf := make([]uint8, 8) + binary.LittleEndian.PutUint64(lenbuf, uint64(8*bytesLen)) + buf = append(buf, uints.NewU8Array(lenbuf)...) + return buf +} + +func (d *digest) Sum() []uints.U8 { + var runningDigest [5]uints.U32 + var buf [64]uints.U8 + copy(runningDigest[:], _seed) + padded := d.padded(len(d.in)) + for i := 0; i < len(padded)/64; i++ { + copy(buf[:], padded[i*64:(i+1)*64]) + runningDigest = ripemd160.Permute(d.uapi, runningDigest, buf) + } + var ret []uints.U8 + for i := range runningDigest { + ret = append(ret, d.uapi.UnpackLSB(runningDigest[i])...) + } + return ret +} + +func (d *digest) Reset() { + d.in = nil +} + +func (d *digest) Size() int { + return 20 +} diff --git a/std/hash/ripemd160/ripemd160_test.go b/std/hash/ripemd160/ripemd160_test.go new file mode 100644 index 000000000..83f37cbd0 --- /dev/null +++ b/std/hash/ripemd160/ripemd160_test.go @@ -0,0 +1,52 @@ +package ripemd160 + +import ( + "fmt" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/uints" + "github.com/consensys/gnark/test" + "golang.org/x/crypto/ripemd160" //nolint staticcheck, backwards compatiblity +) + +type ripemd160Circuit struct { + In []uints.U8 + Expected [20]uints.U8 +} + +func (c *ripemd160Circuit) Define(api frontend.API) error { + h, err := New(api) //nolint G406, false positive, the current package name collides with "golang.org/x/crypto/ripemd160" + if err != nil { + return err + } + uapi, err := uints.New[uints.U32](api) + if err != nil { + return err + } + h.Write(c.In) + res := h.Sum() + if len(res) != len(c.Expected) { + return fmt.Errorf("not 20 bytes") + } + for i := range c.Expected { + uapi.ByteAssertEq(c.Expected[i], res[i]) + } + return nil +} + +func TestRipemd160(t *testing.T) { + bts := make([]byte, 310) + h := ripemd160.New() //nolint G406, false positive, we implement it for EVM compatibility + h.Write(bts) + dgst := h.Sum(nil) + witness := ripemd160Circuit{ + In: uints.NewU8Array(bts), + } + copy(witness.Expected[:], uints.NewU8Array(dgst[:])) + err := test.IsSolved(&ripemd160Circuit{In: make([]uints.U8, len(bts))}, &witness, ecc.BN254.ScalarField()) + if err != nil { + t.Fatal(err) + } +} diff --git a/std/math/uints/hints.go b/std/math/uints/hints.go index 40dbbd991..58edf9fd5 100644 --- a/std/math/uints/hints.go +++ b/std/math/uints/hints.go @@ -15,6 +15,7 @@ func GetHints() []solver.Hint { return []solver.Hint{ andHint, xorHint, + orHint, toBytes, } } @@ -29,6 +30,11 @@ func andHint(_ *big.Int, inputs, outputs []*big.Int) error { return nil } +func orHint(_ *big.Int, inputs, outputs []*big.Int) error { + outputs[0].Or(inputs[0], inputs[1]) + return nil +} + func toBytes(m *big.Int, inputs []*big.Int, outputs []*big.Int) error { if len(inputs) != 2 { return fmt.Errorf("input must be 2 elements") diff --git a/std/math/uints/uint8.go b/std/math/uints/uint8.go index 42b30856f..c97adb935 100644 --- a/std/math/uints/uint8.go +++ b/std/math/uints/uint8.go @@ -84,10 +84,10 @@ type U32 [4]U8 type Long interface{ U32 | U64 } type BinaryField[T U32 | U64] struct { - api frontend.API - xorT, andT *logderivprecomp.Precomputed - rchecker frontend.Rangechecker - allOne U8 + api frontend.API + xorT, andT, orT *logderivprecomp.Precomputed + rchecker frontend.Rangechecker + allOne U8 } func New[T Long](api frontend.API) (*BinaryField[T], error) { @@ -99,11 +99,16 @@ func New[T Long](api frontend.API) (*BinaryField[T], error) { if err != nil { return nil, fmt.Errorf("new and table: %w", err) } + orT, err := logderivprecomp.New(api, orHint, []uint{8}) + if err != nil { + return nil, fmt.Errorf("new or table: %w", err) + } rchecker := rangecheck.New(api) bf := &BinaryField[T]{ api: api, xorT: xorT, andT: andT, + orT: orT, rchecker: rchecker, } // TODO: this is const. add way to init constants @@ -244,6 +249,7 @@ func (bf *BinaryField[T]) twoArgWideFn(tbl *logderivprecomp.Precomputed, a ...T) func (bf *BinaryField[T]) And(a ...T) T { return bf.twoArgWideFn(bf.andT, a...) } func (bf *BinaryField[T]) Xor(a ...T) T { return bf.twoArgWideFn(bf.xorT, a...) } +func (bf *BinaryField[T]) Or(a ...T) T { return bf.twoArgWideFn(bf.orT, a...) } func (bf *BinaryField[T]) not(a U8) U8 { ret := bf.xorT.Query(a.Val, bf.allOne.Val) diff --git a/std/permutation/ripemd160/ripemd160block.go b/std/permutation/ripemd160/ripemd160block.go new file mode 100644 index 000000000..0afdd324f --- /dev/null +++ b/std/permutation/ripemd160/ripemd160block.go @@ -0,0 +1,301 @@ +// Package ripemd160 implements the permutation used in the ripemd160 hash function. +package ripemd160 + +import ( + "github.com/consensys/gnark/std/math/uints" +) + +// work buffer indices and roll amounts for one line +var _n = [80]uint{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13, +} + +var _r = [80]uint{ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6, +} + +// same for the other parallel one +var n_ = [80]uint{ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11, +} + +var r_ = [80]uint{ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11, +} + +var _k = uints.NewU32Array([]uint32{ + 0x50a28be6, + 0x5a827999, 0x5c4dd124, + 0x6ed9eba1, 0x6d703ef3, + 0x8f1bbcdc, 0x7a6d76e9, + 0xa953fd4e, +}) + +func Permute(uapi *uints.BinaryField[uints.U32], currentHash [5]uints.U32, p [64]uints.U8) (newHash [5]uints.U32) { + var x [16]uints.U32 + var alpha, beta uints.U32 + a, b, c, d, e := currentHash[0], currentHash[1], currentHash[2], currentHash[3], currentHash[4] + aa, bb, cc, dd, ee := a, b, c, d, e + for i := 0; i < 16; i++ { + x[i] = uapi.PackLSB(p[4*i], p[4*i+1], p[4*i+2], p[4*i+3]) + } + // round 1 + for i := 0; i < 16; i++ { + // alpha = a + (b ^ c ^ d) + x[_n[i]] + alpha = uapi.Add( + a, + uapi.Xor(b, c, d), + x[_n[i]], + ) + // s := int(_r[i]) + s := int(_r[i]) + // alpha = bits.RotateLeft32(alpha, s) + e + alpha = uapi.Add( + uapi.Lrot(alpha, s), + e, + ) + // beta = bits.RotateLeft32(c, 10) + beta = uapi.Lrot(c, 10) + // a, b, c, d, e = e, alpha, b, beta, d + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + // alpha = aa + (bb ^ (cc | ^dd)) + x[n_[i]] + 0x50a28be6 + alpha = uapi.Add( + aa, + uapi.Xor(bb, + uapi.Or( + cc, + uapi.Not(dd), + ), + ), + x[n_[i]], + _k[0], + ) + // s = int(r_[i]) + s = int(r_[i]) + // alpha = bits.RotateLeft32(alpha, s) + ee + alpha = uapi.Add( + uapi.Lrot(alpha, s), + ee, + ) + // beta = bits.RotateLeft32(cc, 10) + beta = uapi.Lrot(cc, 10) + // aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + } + + // round 2 + for i := 16; i < 32; i++ { + // alpha = a + (b&c | ^b&d) + x[_n[i]] + 0x5a827999 + alpha = uapi.Add( + a, + uapi.Or( + uapi.And(b, c), + uapi.And(uapi.Not(b), d), + ), + x[_n[i]], + _k[1], + ) + // s := int(_r[i]) + s := int(_r[i]) + // alpha = bits.RotateLeft32(alpha, s) + e + alpha = uapi.Add( + uapi.Lrot(alpha, s), + e, + ) + // beta = bits.RotateLeft32(c, 10) + beta = uapi.Lrot(c, 10) + // a, b, c, d, e = e, alpha, b, beta, d + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + // alpha = aa + (bb&dd | cc&^dd) + x[n_[i]] + 0x5c4dd124 + alpha = uapi.Add( + aa, + uapi.Or( + uapi.And(bb, dd), + uapi.And(cc, uapi.Not(dd)), + ), + x[n_[i]], + _k[2], + ) + // s = int(r_[i]) + s = int(r_[i]) + // alpha = bits.RotateLeft32(alpha, s) + ee + alpha = uapi.Add( + uapi.Lrot(alpha, s), + ee, + ) + // beta = bits.RotateLeft32(cc, 10) + beta = uapi.Lrot(cc, 10) + // aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + } + + // round 3 + for i := 32; i < 48; i++ { + // alpha = a + (b | ^c ^ d) + x[_n[i]] + 0x6ed9eba1 + alpha = uapi.Add( + a, + uapi.Xor( + uapi.Or(b, uapi.Not(c)), + d), + x[_n[i]], + _k[3], + ) + // s := int(_r[i]) + s := int(_r[i]) + // alpha = bits.RotateLeft32(alpha, s) + e + alpha = uapi.Add( + uapi.Lrot(alpha, s), + e, + ) + // beta = bits.RotateLeft32(c, 10) + beta = uapi.Lrot(c, 10) + // a, b, c, d, e = e, alpha, b, beta, d + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + // alpha = aa + (bb | ^cc ^ dd) + x[n_[i]] + 0x6d703ef3 + alpha = uapi.Add( + aa, + uapi.Xor( + uapi.Or(bb, uapi.Not(cc)), + dd), + x[n_[i]], + _k[4], + ) + // s = int(r_[i]) + s = int(r_[i]) + // alpha = bits.RotateLeft32(alpha, s) + ee + alpha = uapi.Add( + uapi.Lrot(alpha, s), + ee, + ) + // beta = bits.RotateLeft32(cc, 10) + beta = uapi.Lrot(cc, 10) + // aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + } + + // round 4 + for i := 48; i < 64; i++ { + // alpha = a + (b&d | c&^d) + x[_n[i]] + 0x8f1bbcdc + alpha = uapi.Add( + a, + uapi.Or( + uapi.And(b, d), + uapi.And(c, uapi.Not(d)), + ), + x[_n[i]], + _k[5], + ) + // s := int(_r[i]) + s := int(_r[i]) + // alpha = bits.RotateLeft32(alpha, s) + e + alpha = uapi.Add( + uapi.Lrot(alpha, s), + e, + ) + // beta = bits.RotateLeft32(c, 10) + beta = uapi.Lrot(c, 10) + // a, b, c, d, e = e, alpha, b, beta, d + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + // alpha = aa + (bb&cc | ^bb&dd) + x[n_[i]] + 0x7a6d76e9 + alpha = uapi.Add( + aa, + uapi.Or( + uapi.And(bb, cc), + uapi.And(uapi.Not(bb), dd), + ), + x[n_[i]], + _k[6], + ) + // s = int(r_[i]) + s = int(r_[i]) + // alpha = bits.RotateLeft32(alpha, s) + ee + alpha = uapi.Add( + uapi.Lrot(alpha, s), + ee, + ) + // beta = bits.RotateLeft32(cc, 10) + beta = uapi.Lrot(cc, 10) + // aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + } + + // round 5 + for i := 64; i < 80; i++ { + // alpha = a + (b ^ (c | ^d)) + x[_n[i]] + 0xa953fd4e + alpha = uapi.Add( + a, + uapi.Xor(b, uapi.Or(c, uapi.Not(d))), + x[_n[i]], + _k[7], + ) + // s := int(_r[i]) + s := int(_r[i]) + // alpha = bits.RotateLeft32(alpha, s) + e + alpha = uapi.Add( + uapi.Lrot(alpha, s), + e, + ) + // beta = bits.RotateLeft32(c, 10) + beta = uapi.Lrot(c, 10) + // a, b, c, d, e = e, alpha, b, beta, d + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + // alpha = aa + (bb ^ cc ^ dd) + x[n_[i]] + alpha = uapi.Add( + aa, + uapi.Xor(bb, cc, dd), + x[n_[i]], + ) + // s = int(r_[i]) + s = int(r_[i]) + // alpha = bits.RotateLeft32(alpha, s) + ee + alpha = uapi.Add( + uapi.Lrot(alpha, s), + ee, + ) + // beta = bits.RotateLeft32(cc, 10) + beta = uapi.Lrot(cc, 10) + // aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + } + + // combine results + // dd += c + md.s[1] + // md.s[1] = md.s[2] + d + ee + // md.s[2] = md.s[3] + e + aa + // md.s[3] = md.s[4] + a + bb + // md.s[4] = md.s[0] + b + cc + // md.s[0] = dd + newHash[0] = uapi.Add(currentHash[1], c, dd) + newHash[1] = uapi.Add(currentHash[2], d, ee) + newHash[2] = uapi.Add(currentHash[3], e, aa) + newHash[3] = uapi.Add(currentHash[4], a, bb) + newHash[4] = uapi.Add(currentHash[0], b, cc) + + return +} diff --git a/std/permutation/ripemd160/ripemd160block_native_test.go b/std/permutation/ripemd160/ripemd160block_native_test.go new file mode 100644 index 000000000..416226110 --- /dev/null +++ b/std/permutation/ripemd160/ripemd160block_native_test.go @@ -0,0 +1,132 @@ +package ripemd160 + +// implementation from golang.org/x/crypto/ripemd160 + +import ( + "math/bits" +) + +const blockSize = 64 + +// digest represents the partial evaluation of a checksum. +type digest struct { + s [5]uint32 // running context +} + +func blockGeneric(md *digest, p []byte) int { + n := 0 + var x [16]uint32 + var alpha, beta uint32 + for len(p) >= blockSize { + a, b, c, d, e := md.s[0], md.s[1], md.s[2], md.s[3], md.s[4] + aa, bb, cc, dd, ee := a, b, c, d, e + j := 0 + for i := 0; i < 16; i++ { + x[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24 + j += 4 + } + + // round 1 + i := 0 + for i < 16 { + alpha = a + (b ^ c ^ d) + x[_n[i]] + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb ^ (cc | ^dd)) + x[n_[i]] + 0x50a28be6 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 2 + for i < 32 { + alpha = a + (b&c | ^b&d) + x[_n[i]] + 0x5a827999 + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb&dd | cc&^dd) + x[n_[i]] + 0x5c4dd124 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 3 + for i < 48 { + alpha = a + (b | ^c ^ d) + x[_n[i]] + 0x6ed9eba1 + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb | ^cc ^ dd) + x[n_[i]] + 0x6d703ef3 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 4 + for i < 64 { + alpha = a + (b&d | c&^d) + x[_n[i]] + 0x8f1bbcdc + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb&cc | ^bb&dd) + x[n_[i]] + 0x7a6d76e9 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 5 + for i < 80 { + alpha = a + (b ^ (c | ^d)) + x[_n[i]] + 0xa953fd4e + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb ^ cc ^ dd) + x[n_[i]] + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // combine results + dd += c + md.s[1] + md.s[1] = md.s[2] + d + ee + md.s[2] = md.s[3] + e + aa + md.s[3] = md.s[4] + a + bb + md.s[4] = md.s[0] + b + cc + md.s[0] = dd + + p = p[blockSize:] + n += blockSize + } + return n +} diff --git a/std/permutation/ripemd160/ripemd160block_test.go b/std/permutation/ripemd160/ripemd160block_test.go new file mode 100644 index 000000000..b2408a419 --- /dev/null +++ b/std/permutation/ripemd160/ripemd160block_test.go @@ -0,0 +1,52 @@ +package ripemd160 + +import ( + "math/rand" + "testing" + "time" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/uints" + "github.com/consensys/gnark/test" +) + +type blockCircuit struct { + CurrentDig [5]uints.U32 + In [64]uints.U8 + Expected [5]uints.U32 +} + +func (c *blockCircuit) Define(api frontend.API) error { + uapi, err := uints.New[uints.U32](api) + if err != nil { + return err + } + res := Permute(uapi, c.CurrentDig, c.In) + for i := range c.Expected { + uapi.AssertEq(c.Expected[i], res[i]) + } + return nil +} + +func TestBlockGeneric(t *testing.T) { + assert := test.NewAssert(t) + s := rand.New(rand.NewSource(time.Now().Unix())) //nolint G404, test code + witness := blockCircuit{} + dig := digest{} + var in [64]byte + for i := range dig.s { + dig.s[i] = s.Uint32() + witness.CurrentDig[i] = uints.NewU32(dig.s[i]) + } + for i := range in { + in[i] = byte(s.Uint32() & 0xff) + witness.In[i] = uints.NewU8(in[i]) + } + blockGeneric(&dig, in[:]) + for i := range dig.s { + witness.Expected[i] = uints.NewU32(dig.s[i]) + } + err := test.IsSolved(&blockCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +}