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 }