github.com/consensys/gnark@v0.11.0/constraint/marshal.go (about)

     1  package constraint
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"reflect"
     8  
     9  	"github.com/consensys/gnark/internal/backend/ioutils"
    10  	"github.com/fxamacker/cbor/v2"
    11  	"golang.org/x/sync/errgroup"
    12  )
    13  
    14  // ToBytes serializes the constraint system to a byte slice
    15  // This is not meant to be called directly since the constraint.System is embedded in
    16  // a "curve-typed" system (e.g. bls12-381.system)
    17  func (cs *System) ToBytes() ([]byte, error) {
    18  	// we prepare and write 4 distinct blocks of data;
    19  	// that allow for a more efficient serialization/deserialization (+ parallelism)
    20  	var calldata, instructions, levels []byte
    21  	var g errgroup.Group
    22  	g.Go(func() error {
    23  		var err error
    24  		calldata, err = cs.calldataToBytes()
    25  		return err
    26  	})
    27  	g.Go(func() error {
    28  		var err error
    29  		instructions, err = cs.instructionsToBytes()
    30  		return err
    31  	})
    32  	g.Go(func() error {
    33  		var err error
    34  		levels, err = cs.levelsToBytes()
    35  		return err
    36  	})
    37  	body, err := cs.toBytes()
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	if err := g.Wait(); err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	// header
    47  	h := header{
    48  		levelsLen:       uint64(len(levels)),
    49  		instructionsLen: uint64(len(instructions)),
    50  		calldataLen:     uint64(len(calldata)),
    51  		bodyLen:         uint64(len(body)),
    52  	}
    53  
    54  	// write header
    55  	buf := h.toBytes()
    56  	buf = append(buf, levels...)
    57  	buf = append(buf, instructions...)
    58  	buf = append(buf, calldata...)
    59  	buf = append(buf, body...)
    60  
    61  	return buf, nil
    62  }
    63  
    64  // FromBytes deserializes the constraint system from a byte slice
    65  // This is not meant to be called directly since the constraint.System is embedded in
    66  // a "curve-typed" system (e.g. bls12-381.system)
    67  func (cs *System) FromBytes(data []byte) (int, error) {
    68  	if len(data) < headerLen {
    69  		return 0, errors.New("invalid data length")
    70  	}
    71  
    72  	// read the header which contains the length of each section
    73  	h := new(header)
    74  	h.fromBytes(data)
    75  
    76  	if len(data) < headerLen+int(h.levelsLen)+int(h.instructionsLen)+int(h.calldataLen)+int(h.bodyLen) {
    77  		return 0, errors.New("invalid data length")
    78  	}
    79  
    80  	// read the sections in parallel
    81  	var g errgroup.Group
    82  	g.Go(func() error {
    83  		return cs.levelsFromBytes(data[headerLen : headerLen+h.levelsLen])
    84  	})
    85  
    86  	g.Go(func() error {
    87  		return cs.instructionsFromBytes(data[headerLen+h.levelsLen : headerLen+h.levelsLen+h.instructionsLen])
    88  	})
    89  
    90  	g.Go(func() error {
    91  		return cs.calldataFromBytes(data[headerLen+h.levelsLen+h.instructionsLen : headerLen+h.levelsLen+h.instructionsLen+h.calldataLen])
    92  	})
    93  
    94  	// CBOR decoding of the constraint system (except what we do directly in binary)
    95  	ts := getTagSet()
    96  	dm, err := cbor.DecOptions{
    97  		MaxArrayElements: 2147483647,
    98  		MaxMapPairs:      2147483647,
    99  	}.DecModeWithTags(ts)
   100  
   101  	if err != nil {
   102  		return 0, err
   103  	}
   104  	decoder := dm.NewDecoder(bytes.NewReader(data[headerLen+h.levelsLen+h.instructionsLen+h.calldataLen : headerLen+h.levelsLen+h.instructionsLen+h.calldataLen+h.bodyLen]))
   105  
   106  	if err := decoder.Decode(&cs); err != nil {
   107  		return 0, err
   108  	}
   109  
   110  	if err := cs.CheckSerializationHeader(); err != nil {
   111  		return 0, err
   112  	}
   113  
   114  	switch v := cs.CommitmentInfo.(type) {
   115  	case *Groth16Commitments:
   116  		cs.CommitmentInfo = *v
   117  	case *PlonkCommitments:
   118  		cs.CommitmentInfo = *v
   119  	}
   120  
   121  	if err := g.Wait(); err != nil {
   122  		return 0, err
   123  	}
   124  
   125  	return headerLen + int(h.levelsLen) + int(h.instructionsLen) + int(h.calldataLen) + int(h.bodyLen), nil
   126  }
   127  
   128  func (cs *System) toBytes() ([]byte, error) {
   129  	// CBOR encoding of the constraint system (except what we do directly in binary)
   130  	ts := getTagSet()
   131  	enc, err := cbor.CoreDetEncOptions().EncModeWithTags(ts)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	buf := new(bytes.Buffer)
   136  	encoder := enc.NewEncoder(buf)
   137  
   138  	// encode our object
   139  	err = encoder.Encode(cs)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	return buf.Bytes(), nil
   145  }
   146  
   147  const headerLen = 4 * 8
   148  
   149  type header struct {
   150  	// length in bytes of each sections
   151  	levelsLen       uint64
   152  	instructionsLen uint64
   153  	calldataLen     uint64
   154  	bodyLen         uint64
   155  }
   156  
   157  func (h *header) toBytes() []byte {
   158  	buf := make([]byte, 0, 8*4+h.levelsLen+h.instructionsLen+h.calldataLen+h.bodyLen)
   159  
   160  	buf = binary.LittleEndian.AppendUint64(buf, h.levelsLen)
   161  	buf = binary.LittleEndian.AppendUint64(buf, h.instructionsLen)
   162  	buf = binary.LittleEndian.AppendUint64(buf, h.calldataLen)
   163  	buf = binary.LittleEndian.AppendUint64(buf, h.bodyLen)
   164  
   165  	return buf
   166  }
   167  
   168  func (h *header) fromBytes(buf []byte) {
   169  	h.levelsLen = binary.LittleEndian.Uint64(buf[:8])
   170  	h.instructionsLen = binary.LittleEndian.Uint64(buf[8:16])
   171  	h.calldataLen = binary.LittleEndian.Uint64(buf[16:24])
   172  	h.bodyLen = binary.LittleEndian.Uint64(buf[24:32])
   173  }
   174  
   175  func (cs *System) calldataToBytes() ([]byte, error) {
   176  	// calldata doesn't compress as well as the other sections;
   177  	// it still give a better size to use intcomp.CompressUint32 here,
   178  	// and an even better one to use binary.UVarint
   179  	// but, we keep it simple as it makes deserialization much faster
   180  	// user is still free to compress the final []byte slice if needed.
   181  	buf := make([]byte, 0, 8+len(cs.CallData)*binary.MaxVarintLen32)
   182  	buf = binary.LittleEndian.AppendUint64(buf, uint64(len(cs.CallData)))
   183  	// binary.LittleEndian.PutUint64(buf, uint64(len(cs.CallData)))
   184  	// buf = buf[:8+len(cs.CallData)*4]
   185  	for _, v := range cs.CallData {
   186  		buf = binary.AppendUvarint(buf, uint64(v))
   187  		// binary.LittleEndian.PutUint32(buf[8+i*4:8+i*4+4], v)
   188  	}
   189  	return buf, nil
   190  }
   191  
   192  func (cs *System) instructionsToBytes() ([]byte, error) {
   193  	// prepare the []uint32 separated slices for the packed instructions
   194  	sBlueprintID := make([]uint32, len(cs.Instructions))
   195  	sConstraintOffset := make([]uint32, len(cs.Instructions))
   196  	sWireOffset := make([]uint32, len(cs.Instructions))
   197  	sStartCallData := make([]uint64, len(cs.Instructions))
   198  
   199  	// collect them
   200  	for i, inst := range cs.Instructions {
   201  		sBlueprintID[i] = uint32(inst.BlueprintID)
   202  		sConstraintOffset[i] = inst.ConstraintOffset
   203  		sWireOffset[i] = inst.WireOffset
   204  		sStartCallData[i] = inst.StartCallData
   205  	}
   206  
   207  	// they compress very well due to their nature (sequential integers)
   208  	var buf32 []uint32
   209  	var err error
   210  	var buf bytes.Buffer
   211  	buf.Grow(4 * len(cs.Instructions) * 3)
   212  
   213  	buf32, err = ioutils.CompressAndWriteUints32(&buf, sBlueprintID, buf32)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	buf32, err = ioutils.CompressAndWriteUints32(&buf, sConstraintOffset, buf32)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	_, err = ioutils.CompressAndWriteUints32(&buf, sWireOffset, buf32)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	err = ioutils.CompressAndWriteUints64(&buf, sStartCallData)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	return buf.Bytes(), nil
   232  }
   233  
   234  func (cs *System) levelsToBytes() ([]byte, error) {
   235  	// they compress very well due to their nature (sequential integers)
   236  	var buf32 []uint32
   237  	var buf bytes.Buffer
   238  	var err error
   239  	buf.Grow(4 * len(cs.Instructions))
   240  
   241  	binary.Write(&buf, binary.LittleEndian, uint64(len(cs.Levels)))
   242  	for _, l := range cs.Levels {
   243  		buf32, err = ioutils.CompressAndWriteUints32(&buf, l, buf32)
   244  		if err != nil {
   245  			return nil, err
   246  		}
   247  	}
   248  
   249  	return buf.Bytes(), nil
   250  }
   251  
   252  func (cs *System) levelsFromBytes(in []byte) error {
   253  
   254  	levelsLen := binary.LittleEndian.Uint64(in[:8])
   255  
   256  	in = in[8:]
   257  
   258  	var (
   259  		buf32 []uint32
   260  		err   error
   261  		n     int
   262  	)
   263  
   264  	cs.Levels = make([][]uint32, levelsLen)
   265  	for i := range cs.Levels {
   266  		buf32, n, cs.Levels[i], err = ioutils.ReadAndDecompressUints32(in, buf32)
   267  		if err != nil {
   268  			return err
   269  		}
   270  		in = in[n:]
   271  	}
   272  
   273  	return nil
   274  }
   275  
   276  func (cs *System) instructionsFromBytes(in []byte) error {
   277  
   278  	// read the packed instructions
   279  	var (
   280  		sBlueprintID, sConstraintOffset, sWireOffset []uint32
   281  		sStartCallData                               []uint64
   282  		err                                          error
   283  		n                                            int
   284  		buf32                                        []uint32
   285  	)
   286  	buf32, n, sBlueprintID, err = ioutils.ReadAndDecompressUints32(in, buf32)
   287  	if err != nil {
   288  		return err
   289  	}
   290  	in = in[n:]
   291  	buf32, n, sConstraintOffset, err = ioutils.ReadAndDecompressUints32(in, buf32)
   292  	if err != nil {
   293  		return err
   294  	}
   295  	in = in[n:]
   296  	_, n, sWireOffset, err = ioutils.ReadAndDecompressUints32(in, buf32)
   297  	if err != nil {
   298  		return err
   299  	}
   300  	in = in[n:]
   301  	_, sStartCallData, err = ioutils.ReadAndDecompressUints64(in)
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	// rebuild the instructions
   307  	cs.Instructions = make([]PackedInstruction, len(sBlueprintID))
   308  	for i := range cs.Instructions {
   309  		cs.Instructions[i] = PackedInstruction{
   310  			BlueprintID:      BlueprintID(sBlueprintID[i]),
   311  			ConstraintOffset: sConstraintOffset[i],
   312  			WireOffset:       sWireOffset[i],
   313  			StartCallData:    sStartCallData[i],
   314  		}
   315  	}
   316  
   317  	return nil
   318  }
   319  
   320  func (cs *System) calldataFromBytes(buf []byte) error {
   321  	calldataLen := binary.LittleEndian.Uint64(buf[:8])
   322  	cs.CallData = make([]uint32, calldataLen)
   323  	buf = buf[8:]
   324  	for i := uint64(0); i < calldataLen; i++ {
   325  		v, n := binary.Uvarint(buf[:min(len(buf), binary.MaxVarintLen64)])
   326  		if n <= 0 {
   327  			return errors.New("invalid calldata")
   328  		}
   329  		cs.CallData[i] = uint32(v)
   330  		buf = buf[n:]
   331  	}
   332  	return nil
   333  }
   334  
   335  func getTagSet() cbor.TagSet {
   336  	// temporary for refactor
   337  	ts := cbor.NewTagSet()
   338  	// https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
   339  	// 65536-15309735 Unassigned
   340  	tagNum := uint64(5309735)
   341  	addType := func(t reflect.Type) {
   342  		if err := ts.Add(
   343  			cbor.TagOptions{EncTag: cbor.EncTagRequired, DecTag: cbor.DecTagRequired},
   344  			t,
   345  			tagNum,
   346  		); err != nil {
   347  			panic(err)
   348  		}
   349  		tagNum++
   350  	}
   351  
   352  	addType(reflect.TypeOf(BlueprintGenericHint{}))
   353  	addType(reflect.TypeOf(BlueprintGenericR1C{}))
   354  	addType(reflect.TypeOf(BlueprintGenericSparseR1C{}))
   355  	addType(reflect.TypeOf(BlueprintSparseR1CAdd{}))
   356  	addType(reflect.TypeOf(BlueprintSparseR1CMul{}))
   357  	addType(reflect.TypeOf(BlueprintSparseR1CBool{}))
   358  	addType(reflect.TypeOf(BlueprintLookupHint{}))
   359  	addType(reflect.TypeOf(Groth16Commitments{}))
   360  	addType(reflect.TypeOf(PlonkCommitments{}))
   361  
   362  	return ts
   363  }