Skip to content

Commit

Permalink
gnark-crypto, eip-196: malformed input handling (#188)
Browse files Browse the repository at this point in the history
* interpret both short input 
* add test cases for point not on curve, use IntByReference to indicate error and output length rather than static vals
---------

Signed-off-by: garyschulte <[email protected]>
  • Loading branch information
garyschulte authored Jul 11, 2024
1 parent bc84494 commit 107c0e6
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ input,result,gas,notes
1ca0717a8dfb9c3940a731d7c52f1699f64fe05e76189a91dc622e8fafd99de62313a1df5b32b17c21e53e2d0a1ff3eeac6ab4359a9f86e51b1c236f414d87ea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,1ca0717a8dfb9c3940a731d7c52f1699f64fe05e76189a91dc622e8fafd99de62313a1df5b32b17c21e53e2d0a1ff3eeac6ab4359a9f86e51b1c236f414d87ea,150,
1234,,150,invalid input parameters, Point 0 is not on curve
0174fc233104c2ad4f56a8396b8c1b7d9c6ad10bffc70761c5e8f5280862f137029733a9f20a4cdbb7ae9c5dd1adf6ccc7fe3439d7dc71093af0656ae0ca0f290964773f12e2292f332306374f957d10,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,150,invalid input parameters, Point 1 is not on curve
0174fc233104c2ad4f56a8396b8c1b7d9c6ad10bffc70761c5e8f5280862f137029733a9f20a4cdbb7ae9c5dd1adf6ccc7fe3439d7dc71093af0656ae0ca0f290964773f12e2292f332306374f957d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,150,invalid input parameters, Point 1 is not on curve
67376aad340c93eb0fc9bc8e040dc691bd00e426c6456b4d13079e7f1dbb3da847eb0fc271cd23da50c6ebc261928abb1af9bfcea998791e18af1817b06221e1fe708d2f4224275523fcd37460a310ce4b56f1694dfe36280410f0fb6efc5f47b85662e5b08d881242a72acbc2c8e2fa71ac593be977ad3e090c8158aace0247,,150,invalid input parameters, Failed to parse Fp element
,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,150,
ff000000000000000000000000000000000000000000000000000000000000ff,,150,invalid input parameters, Failed to parse Fp element
1470b80a6d5de1470aeadfa0b2eb913d9286bd966aa8a99b09879f3b10c985d02432b8ccd7083d421127dac6ad90bd569e763810e32d37114a786c7645864647,1470b80a6d5de1470aeadfa0b2eb913d9286bd966aa8a99b09879f3b10c985d02432b8ccd7083d421127dac6ad90bd569e763810e32d37114a786c7645864647,150,
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ input,result,gas,notes
1c2cb2b4504b19c7e073679432e625f2706b7c4728cd9bd3ce36579f4de2f3902c8605f723ac2f73baa15eac674f62ab06c79809aa4a4be3391c4d41d5a6e62ca0a73a5b559cd0384ca11d444e34f40e04f2080483db9e54ae09e44ec19c26a1,1147da8fc5774b5ef94fda682437b04f9b6aea72987d1269496769baf7e67dc729fe144eab44c6da4c851123165cd6506ce3e10b7691b8bf11b757e2ae058fcb,6000,
0d6ad8e12b4f61e3e2a2252ce11428941f2a84b7f0a821cb8cc7699303bd4fec2247870562618fd8d6169072d9b33614d2acf800b3ba0ff68ef8d5fd4d6c250d3e70b3bed17894f958579644c83fa9d485121d580e2b061c697e68f950297768,0be6d75e2fe2887835d396dae11321ca7c53083abd6a0b270ee1c087593517aa2ffd1bad577de7cf2b19b82bfff0c66e2afbfb79a72cbe834290437f3caf2f21,6000,
d2acf800b3ba0ff68ef8d5fd4d6c250d3e70b3bed17894f958579644c83fa9d485121d580e2b061c697e68f9502977680d6ad8e12b4f61e3e2a2252ce11428941f2a84b7f0a821cb8cc7699303bd4fec2247870562618fd8d6169072d9b33614,,6000,invalid input parameters, Failed to parse Fp element
02acf800b3ba0ff68ef8d5fd4d6c250d3e70b3bed17894f958579644c83fa9d405121d580e2b061c697e68f9502977680d6ad8e12b4f61e3e2a2252ce11428941f2a84b7f0a821cb8cc7699303bd4fec2247870562618fd8d6169072d9b33614,,6000,invalid input parameters, Point is not on curve
,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,6000,
1A87B0584CE92F4593D161480614F2989035225609F08058CCFA3D0F940FEBE31A2F3C951F6DADCC7EE9007DFF81504B0FCD6D7CF59996EFDC33D92BF7F9F8F60100000000000000000000000000000000,0x220cb1540fa85ba04d863dca86de9359a43ac9fc084aebb9f2a936d989abbb602ccdc6c020dd2cf78332132b3f1d1122391b515035623cd6f53d4aea24ea2466,6000,
1A87B0584CE92F4593D161480614F2989035225609F08058CCFA3D0F940FEBE31A2F3C951F6DADCC7EE9007DFF81504B0FCD6D7CF59996EFDC33D92BF7F9F8F60000000000000000000000000000000100000000000000000000000000000000,1051acb0700ec6d42a88215852d582efbaef31529b6fcbc3277b5c1b300f5cf0135b2394bb45ab04b8bd7611bd2dfe1de6a4e6e2ccea1ea1955f577cd66af85b,6000,
171 changes: 89 additions & 82 deletions gnark/gnark-jni/gnark-eip-196.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import (
"github.com/consensys/gnark-crypto/ecc/bn254/fp"
)

var ErrMalformedPoint = errors.New("invalid input parameters, invalid point encoding")
var ErrPointNotInField = errors.New("invalid input parameters, point not in field")
var ErrPointOnCurveCheckFailed = errors.New("invalid point: point is not on curve")
var ErrMalformedPointEIP196 = errors.New("invalid point encoding")
var ErrInvalidInputPairingLengthEIP196 = errors.New("invalid input parameters, invalid input length for pairing")
var ErrPointNotInFieldEIP196 = errors.New("point not in field")
var ErrPointOnCurveCheckFailedEIP196 = errors.New("point is not on curve")

const (
EIP196PreallocateForResult = 128
Expand All @@ -40,65 +41,52 @@ var bn254Modulus = new(big.Int).SetBytes([]byte{
})

// Predefine a zero slice of length 16
var zeroSlice = make([]byte, 16)
var zeroEIP196Slice = make([]byte, 16)

//export eip196altbn128G1Add
func eip196altbn128G1Add(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cInputLen, cOutputLen, cErrorLen C.int) C.int {
func eip196altbn128G1Add(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cInputLen C.int, cOutputLen, cErrorLen *C.int) C.int {
inputLen := int(cInputLen)
errorLen := int(cErrorLen)

if (inputLen > 2*EIP196PreallocateForG1) {
// trunc if input too long
inputLen = 2*EIP196PreallocateForG1
}
errorLen := (*int)(unsafe.Pointer(cErrorLen))
outputLen := (*int)(unsafe.Pointer(cOutputLen))

// Convert error C pointers to Go slices
errorBuf := castErrorBuffer(javaErrorBuf, errorLen)
errorBuf := castErrorBufferEIP196(javaErrorBuf, errorLen)

// check we have input size sufficient for a G1Affine
if inputLen < EIP196PreallocateForG1 {

// if not, check the X point and return an error if it is in the field, edge case
if inputLen >= EIP196PreallocateForFp {
input := (*[EIP196PreallocateForFp]byte)(unsafe.Pointer(javaInputBuf))[:EIP196PreallocateForFp:EIP196PreallocateForFp]
if !checkInField(input[:EIP196PreallocateForFp]) {
copy(errorBuf, ErrPointNotInField.Error())
return 1
}
}
// otherwise if we do not have complete input, return 0
return 0
if (inputLen > 2*EIP196PreallocateForG1) {
// trunc if input too long
inputLen = 2*EIP196PreallocateForG1
}

// Convert input C pointers to Go slices
input := (*[2*EIP196PreallocateForG1]byte)(unsafe.Pointer(javaInputBuf))[:inputLen:inputLen]

if (inputLen == 0) {
*outputLen = 0
return 0
}

// generate p0 g1 affine
p0, err := safeUnmarshal(input[:64])
p0, err := safeUnmarshalEIP196(input, 0)

if err != nil {
copy(errorBuf, "invalid input parameters, " + err.Error())
dryError(err, errorBuf, outputLen, errorLen)
return 1
}

if inputLen < 2*EIP196PreallocateForG1 {
// if incomplete input is all zero, return p0
if isAllZero(input[64:inputLen]) {
if isAllZeroEIP196(input, 64) {
ret := p0.Marshal()
g1AffineEncode(ret, javaOutputBuf)
*outputLen = EIP196PreallocateForG1
return 0;
} else {
// else return an incomplete input error
copy(errorBuf, ErrMalformedPoint.Error())
return 1
}
}

// generate p1 g1 affine
p1, err := safeUnmarshal(input[64:])
p1, err := safeUnmarshalEIP196(input, 64)

if err != nil {
copy(errorBuf, "invalid input parameters, " + err.Error())
dryError(err, errorBuf, outputLen, errorLen)
return 1
}

Expand All @@ -108,30 +96,23 @@ func eip196altbn128G1Add(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cInp
// marshal the resulting point and encode directly to the output buffer
ret := result.Marshal()
g1AffineEncode(ret, javaOutputBuf)
*outputLen = EIP196PreallocateForG1
return 0

}

//export eip196altbn128G1Mul
func eip196altbn128G1Mul(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cInputLen, cOutputLen, cErrorLen C.int) C.int {
func eip196altbn128G1Mul(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cInputLen C.int, cOutputLen, cErrorLen *C.int) C.int {
inputLen := int(cInputLen)
errorLen := int(cErrorLen)
errorLen := (*int)(unsafe.Pointer(cErrorLen))
outputLen := (*int)(unsafe.Pointer(cOutputLen))

// Convert error C pointers to Go slices
errorBuf := castErrorBuffer(javaErrorBuf, errorLen)
errorBuf := castErrorBufferEIP196(javaErrorBuf, errorLen)

if inputLen < EIP196PreallocateForG1 {

// check the X point and return an error if it is in the field, edge case
if inputLen >= EIP196PreallocateForFp {
input := (*[EIP196PreallocateForFp]byte)(unsafe.Pointer(javaInputBuf))[:EIP196PreallocateForFp:EIP196PreallocateForFp]
if !checkInField(input[:EIP196PreallocateForFp]) {
copy(errorBuf, ErrPointNotInField.Error())
return 1
}
}

// otherwise if we do not have complete input, return 0
// if we do not have complete input, return 0
*outputLen = 0
return 0
}

Expand All @@ -144,11 +125,10 @@ func eip196altbn128G1Mul(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cInp
input := (*[EIP196PreallocateForG1 + EIP196PreallocateForScalar]byte)(unsafe.Pointer(javaInputBuf))[:inputLen:inputLen]

// generate p0 g1 affine
var p0 bn254.G1Affine
err := p0.Unmarshal(input[:64])
p0, err := safeUnmarshalEIP196(input, 0)

if err != nil {
copy(errorBuf, err.Error())
dryError(err, errorBuf, outputLen, errorLen)
return 1
}

Expand All @@ -164,38 +144,41 @@ func eip196altbn128G1Mul(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cInp
scalar.SetBytes(scalarBytes[:])

// multiply g1 point by scalar
result := p0.ScalarMultiplication(&p0, scalar)
result := p0.ScalarMultiplication(p0, scalar)

// marshal the resulting point and encode directly to the output buffer
ret := result.Marshal()
g1AffineEncode(ret, javaOutputBuf)
*outputLen = EIP196PreallocateForG1
return 0
}

//export eip196altbn128Pairing
func eip196altbn128Pairing(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cInputLen, cOutputLen, cErrorLen C.int) C.int {
func eip196altbn128Pairing(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cInputLen C.int, cOutputLen, cErrorLen *C.int) C.int {
inputLen := int(cInputLen)
outputLen := int(cOutputLen)
errorLen := int(cErrorLen)
errorLen := (*int)(unsafe.Pointer(cErrorLen))
outputLen := (*int)(unsafe.Pointer(cOutputLen))

// Convert error C pointers to Go slices
output := castBuffer(javaOutputBuf, outputLen)
output := castBufferEIP196(javaOutputBuf, outputLen)

// Convert error C pointers to Go slices
errorBuf := castErrorBuffer(javaErrorBuf, errorLen)
errorBuf := castErrorBufferEIP196(javaErrorBuf, errorLen)

*outputLen = 32

if inputLen == 0 {
output[31]=0x01
return 0
}

if inputLen % (EIP196PreallocateForG2 + EIP196PreallocateForG1) != 0 {
copy(errorBuf, "invalid input parameters, invalid input length for pairing\x00")
dryError(ErrInvalidInputPairingLengthEIP196, errorBuf, outputLen, errorLen)
return 1
}

// Convert input C pointers to Go slice
input := castBufferToSlice(unsafe.Pointer(javaInputBuf), inputLen)
input := castBufferToSliceEIP196(unsafe.Pointer(javaInputBuf), inputLen)

var pairCount = inputLen / (EIP196PreallocateForG2 + EIP196PreallocateForG1)
g1Points := make([]bn254.G1Affine, pairCount)
Expand All @@ -209,7 +192,7 @@ func eip196altbn128Pairing(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cI
err := g1.Unmarshal(input[i*192:i*192+64])

if err != nil {
copy(errorBuf, err.Error())
dryError(err, errorBuf, outputLen, errorLen)
return 1
}

Expand All @@ -218,7 +201,7 @@ func eip196altbn128Pairing(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cI
err = g2.Unmarshal(input[i*192+64:(i+1)*192])

if err != nil {
copy(errorBuf, err.Error())
dryError(err, errorBuf, outputLen, errorLen)
return 1
}

Expand All @@ -229,15 +212,14 @@ func eip196altbn128Pairing(javaInputBuf, javaOutputBuf, javaErrorBuf *C.char, cI

isOne, err := bn254.PairingCheck(g1Points, g2Points)
if err != nil {
copy(errorBuf, err.Error())
dryError(err, errorBuf, outputLen, errorLen)
return -1
}

if (isOne) {
// respond with 1 if pairing check was true, leave 0's intact otherwise
output[31]=0x01
}

return 0

}
Expand All @@ -257,25 +239,40 @@ func g1AffineEncode(g1Point []byte, output *C.char) (error) {
return nil
}

func safeUnmarshal(input []byte) (*bn254.G1Affine, error) {
func safeUnmarshalEIP196(input []byte, offset int) (*bn254.G1Affine, error) {
var g1 bn254.G1Affine
err := g1.X.SetBytesCanonical(input[:32])

if (len(input) - offset < 64) {
return nil, ErrMalformedPointEIP196
}

if !checkInFieldEIP196(input[offset: offset + 32]) {
return nil, ErrPointNotInFieldEIP196
}

err := g1.X.SetBytesCanonical(input[offset:offset + 32])

if (err == nil) {
err := g1.Y.SetBytesCanonical(input[32:64])

if !checkInFieldEIP196(input[offset + 32: offset + 64]) {
return nil, ErrPointNotInFieldEIP196
}
err := g1.Y.SetBytesCanonical(input[offset + 32:offset + 64])
if (err == nil) {
if (!g1.IsOnCurve()) {
return nil, ErrPointOnCurveCheckFailedEIP196
}
return &g1, nil
}
}
if (!g1.IsOnCurve()) {
return nil, ErrPointOnCurveCheckFailed
}


return nil, err
}

// checkInField checks that an element is in the field, not-in-field will normally
// be caught during unmarshal, but here in case of no-op calls of a single parameter
func checkInField(data []byte) bool {
func checkInFieldEIP196(data []byte) bool {

// Convert the byte slice to a big.Int
elem := new(big.Int).SetBytes(data)
Expand All @@ -285,16 +282,26 @@ func checkInField(data []byte) bool {
}

// isAllZero checks if all elements in the byte slice are zero
func isAllZero(data []byte) bool {
for _, b := range data {
if b != 0 {
return false
func isAllZeroEIP196(data []byte, offset int) bool {
if len(data) > 64 {
slice := data [offset:]
for _, b := range slice {
if b != 0 {
return false
}
}
}
return true
}

func castBufferToSlice(buf unsafe.Pointer, length int) []byte {
func dryError(err error, errorBuf []byte, outputLen, errorLen *int) {
errStr := "invalid input parameters, " + err.Error();
copy(errorBuf, errStr)
*outputLen = 0
*errorLen = len(errStr)
}

func castBufferToSliceEIP196(buf unsafe.Pointer, length int) []byte {
var slice []byte
// Obtain the slice header
header := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
Expand All @@ -305,17 +312,17 @@ func castBufferToSlice(buf unsafe.Pointer, length int) []byte {
return slice
}

func castBuffer(javaOutputBuf *C.char, length int) []byte {
bufSize := length
if bufSize < EIP196PreallocateForResult {
func castBufferEIP196(javaOutputBuf *C.char, length *int) []byte {
bufSize := *length
if bufSize != EIP196PreallocateForResult {
bufSize = EIP196PreallocateForResult
}
return (*[EIP196PreallocateForResult]byte)(unsafe.Pointer(javaOutputBuf))[:bufSize:bufSize]
}

func castErrorBuffer(javaOutputBuf *C.char, length int) []byte {
bufSize := length
if bufSize < EIP196PreallocateForError {
func castErrorBufferEIP196(javaOutputBuf *C.char, length *int) []byte {
bufSize := *length
if bufSize != EIP196PreallocateForError {
bufSize = EIP196PreallocateForError
}
return (*[EIP196PreallocateForError]byte)(unsafe.Pointer(javaOutputBuf))[:bufSize:bufSize]
Expand Down
Loading

0 comments on commit 107c0e6

Please sign in to comment.