diff --git a/cmd/casm-inspect/disasm.go b/cmd/casm-inspect/disasm.go new file mode 100644 index 000000000..21ee4534f --- /dev/null +++ b/cmd/casm-inspect/disasm.go @@ -0,0 +1,122 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/NethermindEth/cairo-vm-go/pkg/disasm" + f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" + + "github.com/urfave/cli/v2" +) + +// disasmProgram implements a "disasm" subcommand. +type disasmProgram struct { + pathToFile string + bytecodeKey string + + rawCasm map[string]any + + bytecode []*f.Element + + disassembled *disasm.Program +} + +func (p *disasmProgram) Action(ctx *cli.Context) error { + p.pathToFile = ctx.Args().Get(0) + if p.pathToFile == "" { + return fmt.Errorf("path to casm file not set") + } + + type step struct { + name string + fn func() error + } + steps := []step{ + {"unmarshal casm file", p.unmarshalCasmFileStep}, + {"load bytecode", p.loadBytecodeStep}, + {"disassemble", p.disassembleStep}, + {"print", p.printStep}, + } + for _, s := range steps { + if err := s.fn(); err != nil { + return fmt.Errorf("%s: %w", s.name, err) + } + } + + return nil +} + +func (p *disasmProgram) unmarshalCasmFileStep() error { + data, err := os.ReadFile(p.pathToFile) + if err != nil { + return err + } + if err := json.Unmarshal(data, &p.rawCasm); err != nil { + return err + } + return nil +} + +func (p *disasmProgram) loadBytecodeStep() error { + // Since different versions of CASM files may store bytecode at different places + // (e.g. "data" in Cairo0 and "bytecode" in Cairo1), + // we allow the user to specify the bytecode array location. + // By default, this value will be equal to the default supported version location + // (Cairo0 for now and Cairo1 in the future). + keys := strings.Split(p.bytecodeKey, ".") + + v := lookupKeys(p.rawCasm, keys...) + if v == nil { + return fmt.Errorf("key %q doesn't lead to a bytecode", p.bytecodeKey) + } + + slice, ok := v.([]any) + if !ok { + return fmt.Errorf("%q: expected a slice of strings", p.bytecodeKey) + } + + p.bytecode = make([]*f.Element, 0, len(slice)) + for i, s := range slice { + s, ok := s.(string) + if !ok { + return fmt.Errorf("%q: expected a slice of strings, found %T", p.bytecodeKey, slice[i]) + } + felt, err := new(f.Element).SetString(s) + if err != nil { + return fmt.Errorf("%q[%d]: parse %q: %w", p.bytecodeKey, i, s, err) + } + p.bytecode = append(p.bytecode, felt) + } + + return nil +} + +func (p *disasmProgram) disassembleStep() error { + prog, err := disasm.FromBytecode(disasm.Config{ + Bytecode: p.bytecode, + Indent: 4, + }) + if err != nil { + return err + } + p.disassembled = prog + return nil +} + +func (p *disasmProgram) printStep() error { + for _, l := range p.disassembled.Lines { + if len(l.Text) == 0 { + fmt.Printf("// %s\n", strings.Join(l.Comments, "; ")) + continue + } + if len(l.Comments) == 0 { + fmt.Printf("%s;\n", l.Text) + } else { + fmt.Printf("%s; // %s\n", l.Text, strings.Join(l.Comments, "; ")) + } + } + return nil +} diff --git a/cmd/casm-inspect/inst_fields.go b/cmd/casm-inspect/inst_fields.go new file mode 100644 index 000000000..528693682 --- /dev/null +++ b/cmd/casm-inspect/inst_fields.go @@ -0,0 +1,77 @@ +package main + +import ( + "errors" + "fmt" + "strings" + + f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" + "github.com/urfave/cli/v2" +) + +// instFieldsProgram implements an "inst-fields" subcommand. +type instFieldsProgram struct{} + +func (p *instFieldsProgram) Action(ctx *cli.Context) error { + s := ctx.Args().Get(0) + if s == "" { + return errors.New("expected 1 non-empty positional argument") + } + + felt, err := new(f.Element).SetString(s) + if err != nil { + return fmt.Errorf("parsing %q argument: %w", s, err) + } + if !felt.IsUint64() { + return errors.New("instruction bytes overflow uint64") + } + + u64 := felt.Uint64() + + fmt.Printf("uint64 value: %v\n", u64) + + // We don't use the assembler's package code here to make it possible + // to use this dumper tool even if assembler package can't validate + // the input. Unlike the assembler package, this tool doesn't care + // if the provided bits are valid or not. + // It will split them into "fields" expected by the CASM instruction encoding. + + type instField struct { + name string + width int // in bits + signed bool + } + encodingList := []instField{ + {"off_dst", 16, true}, + {"off_op0", 16, true}, + {"off_op1", 16, true}, + {"dst_reg", 1, false}, + {"op0_reg", 1, false}, + {"op1_src", 3, false}, + {"res_logic", 2, false}, + {"pc_update", 3, false}, + {"ap_update", 2, false}, + {"opcode", 3, false}, + } + + const onesMask = ^uint64(0) + + var chunks []string + + offset := int(0) + for _, field := range encodingList { + mask := onesMask >> (64 - field.width) + fieldBits := (u64 >> offset) & mask + if field.signed { + fmt.Printf("%s: %v (%b)\n", field.name, int16(fieldBits), fieldBits) + } else { + fmt.Printf("%s: %v (%b)\n", field.name, fieldBits, fieldBits) + } + chunks = append(chunks, fmt.Sprintf("%b", fieldBits)) + offset += field.width + } + + fmt.Printf("bits: %s\n", strings.Join(chunks, " ")) + + return nil +} diff --git a/cmd/casm-inspect/main.go b/cmd/casm-inspect/main.go new file mode 100644 index 000000000..c194c7c2c --- /dev/null +++ b/cmd/casm-inspect/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + disasm := &disasmProgram{} + instFields := &instFieldsProgram{} + + app := &cli.App{ + Name: "casm-inspect", + Usage: "casm-inspect [args...]", + Description: "A cairo zero file inspector", + EnableBashCompletion: true, + Suggest: true, + DefaultCommand: "help", + Commands: []*cli.Command{ + { + Name: "inst-fields", + Usage: "inst-fields 0xa0680017fff8000", + Description: "print CASM instruction fields", + Action: instFields.Action, + }, + { + Name: "disasm", + Usage: "disasm compiled_cairo0.json", + Description: "disassemble the casm from the compiled cairo program", + Action: disasm.Action, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "bytecode", + Usage: "a JSON key containing CASM bytecode (period-separated for multi-keys)", + Required: false, + Value: "data", + Destination: &disasm.bytecodeKey, + }, + }, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/casm-inspect/utils.go b/cmd/casm-inspect/utils.go new file mode 100644 index 000000000..7a2b782dd --- /dev/null +++ b/cmd/casm-inspect/utils.go @@ -0,0 +1,16 @@ +package main + +// lookupKeys performs a multi-level map search given a list of keys to query. +// Given a map like {"a": {"b": {"c": 10}}} and keys ["a", "b", "c"] this +// function will return 10 (a value of the deepest lookup). +func lookupKeys(m map[string]any, keys ...string) any { + var current any = m + for _, k := range keys { + asMap, ok := current.(map[string]any) + if !ok { + return nil + } + current = asMap[k] + } + return current +} diff --git a/pkg/disasm/casm.go b/pkg/disasm/casm.go new file mode 100644 index 000000000..2840afbf7 --- /dev/null +++ b/pkg/disasm/casm.go @@ -0,0 +1,36 @@ +package disasm + +import ( + "github.com/NethermindEth/cairo-vm-go/pkg/assembler" + f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" +) + +type casmInstruction struct { + *assembler.Instruction + + arg *f.Element + bytecodeOffset int64 +} + +func (inst *casmInstruction) JumpTarget() (int64, bool) { + if inst.Opcode == assembler.OpCodeRet { + return 0, false + } + if inst.PcUpdate == assembler.PcUpdateNextInstr { + return 0, false + } + + offset := feltToInt64(inst.arg) + if inst.PcUpdate == assembler.PcUpdateJump { + return offset, true + } + return inst.bytecodeOffset + offset, true +} + +func (inst *casmInstruction) Size() int64 { + // Note: OpCodeCall also has an immediate (call target). + if inst.Op1Source == assembler.Imm { + return 2 + } + return 1 +} diff --git a/pkg/disasm/casm_formatter.go b/pkg/disasm/casm_formatter.go new file mode 100644 index 000000000..f20965d90 --- /dev/null +++ b/pkg/disasm/casm_formatter.go @@ -0,0 +1,127 @@ +package disasm + +import ( + "bytes" + "fmt" + "io" + "strings" + + "github.com/NethermindEth/cairo-vm-go/pkg/assembler" +) + +type casmFormatter struct { + labels map[int64]int + funcLabels map[int64]int +} + +func (cf *casmFormatter) printInstruction(w io.Writer, inst casmInstruction) error { + var buf bytes.Buffer + + switch inst.Opcode { + case assembler.OpCodeRet: + buf.WriteString("ret") + + case assembler.OpCodeCall: + var callSuffix string + switch inst.PcUpdate { + case assembler.PcUpdateJump: + callSuffix = "abs" + case assembler.PcUpdateJumpRel: + callSuffix = "rel" + } + fmt.Fprintf(&buf, "call %s %+d", callSuffix, feltToInt64(inst.arg)) + + case assembler.OpCodeAssertEq: + buf.WriteString(cf.formatMemoryOperand(inst.DstRegister, int(inst.OffDest))) + buf.WriteString(" = ") + + if inst.Res != assembler.Op1 { + buf.WriteString(cf.formatMemoryOperand(inst.Op0Register, int(inst.OffOp0))) + } + switch inst.Res { + case assembler.AddOperands: + buf.WriteString(" + ") + case assembler.MulOperands: + buf.WriteString(" * ") + } + + buf.WriteString(cf.formatOperand1(inst)) + + case assembler.OpCodeNop: + // Jumps use the same opcode=0. + switch inst.PcUpdate { + case assembler.PcUpdateJump: + buf.WriteString("jmp abs " + cf.formatOperand1(inst)) + case assembler.PcUpdateJumpRel: + buf.WriteString("jmp rel " + cf.formatOperand1(inst)) + case assembler.PcUpdateJnz: + fmt.Fprintf(&buf, "jmp rel %s if %s != 0", + cf.formatOperand1(inst), + cf.formatMemoryOperand(inst.DstRegister, int(inst.OffDest))) + } + if inst.ApUpdate == assembler.AddRes && inst.Op1Source == assembler.Imm { + // This is an "ap_add" pseudo-instruction encoding. + // + // See Display impl for AddApInstruction type in instruction.rs; + // https://github.com/starkware-libs/cairo/blob/797781d8365445ad4a1ae8202881a61b6656bb98/crates/cairo-lang-casm/src/instructions.rs#L198 + fmt.Fprintf(&buf, "ap += %d", feltToInt64(inst.arg)) + } + + default: + return fmt.Errorf("unexpected opcode: %v", inst.Opcode) + } + + switch inst.ApUpdate { + case assembler.Add1: + buf.WriteString(", ap++") + } + + _, err := w.Write(buf.Bytes()) + return err +} + +func (cf *casmFormatter) formatOperand1(inst casmInstruction) string { + var buf strings.Builder + + switch inst.Op1Source { + case assembler.ApPlusOffOp1: + buf.WriteString(cf.formatMemoryOperand(assembler.Ap, int(inst.OffOp1))) + case assembler.FpPlusOffOp1: + buf.WriteString(cf.formatMemoryOperand(assembler.Fp, int(inst.OffOp1))) + case assembler.Imm: + buf.WriteString(inst.arg.String()) + case assembler.Op0: + // Things like [[fp+10]+20]. + buf.WriteString(cf.formatMemoryOperand2(inst.Op0Register, int(inst.OffOp0), int(inst.OffOp1))) + } + + return buf.String() +} + +func (cf *casmFormatter) formatMemoryOperand(reg assembler.Register, offset int) string { + var buf strings.Builder + buf.WriteString("[") + buf.WriteString(strings.ToLower(reg.String())) + if offset != 0 { + fmt.Fprintf(&buf, "%+d", offset) + } + buf.WriteString("]") + return buf.String() +} + +func (cf *casmFormatter) formatMemoryOperand2(reg assembler.Register, offset, offset2 int) string { + var buf strings.Builder + + buf.WriteString("[[") + buf.WriteString(strings.ToLower(reg.String())) + if offset != 0 { + fmt.Fprintf(&buf, "%+d", offset) + } + buf.WriteString("]") + if offset2 != 0 { + fmt.Fprintf(&buf, "%+d", offset2) + } + buf.WriteString("]") + + return buf.String() +} diff --git a/pkg/disasm/disasm.go b/pkg/disasm/disasm.go new file mode 100644 index 000000000..74fef9dda --- /dev/null +++ b/pkg/disasm/disasm.go @@ -0,0 +1,28 @@ +package disasm + +import ( + f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" +) + +type Program struct { + Lines []Line +} + +type Line struct { + Text string + Comments []string +} + +type Config struct { + Bytecode []*f.Element + + Indent int +} + +func FromBytecode(config Config) (*Program, error) { + d := &disassembler{ + bytecode: config.Bytecode, + config: config, + } + return d.Disassemble() +} diff --git a/pkg/disasm/disasm_test.go b/pkg/disasm/disasm_test.go new file mode 100644 index 000000000..2330b8464 --- /dev/null +++ b/pkg/disasm/disasm_test.go @@ -0,0 +1,89 @@ +package disasm_test + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/NethermindEth/cairo-vm-go/pkg/assembler" + "github.com/NethermindEth/cairo-vm-go/pkg/disasm" + "github.com/stretchr/testify/assert" +) + +func TestDisasm(t *testing.T) { + tests, err := os.ReadDir("testdata") + if err != nil { + t.Fatal(err) + } + + // This code tests both CASM parser and disassembler. + // * We take some pre-existing CASM file from the test suite + // * Then it's parsed by our assembler package (instList) + // * The result of our parser is used as a disasm input + // * The disassembly is then stringified to get a new CASM input file + // * This new CASM file is given to our assembler package (instList2) + // * As a final step, instList and instList2 slices are compared + // + // If they're equal, it's a good sign and we have a high chance + // of asm+disasm not losing important information. + // + // We could also check for testCode and testCode2 being identical, + // but apart from the formatting issues (easy to solve) there are + // equivalent ways to write some expressions, e.g.: + // > [fp + -3] vs [fp - 3] + // > [ap+0] vs [ap] + // Both of them encode the same dereference expr, just spelled differently. + // But anyway, with an extra flag we can mark some of the tests as + // "ok to check for textual equallity" (see checkTexts map below). + + checkTexts := map[string]struct{}{ + "simple.casm": {}, + "hash_chain_pretty.casm": {}, + } + + disasmProgToString := func(p *disasm.Program) string { + // Ignore the comments. + var buf strings.Builder + for _, l := range p.Lines { + if len(l.Text) == 0 { + // This is a comment-only line. + continue + } + fmt.Fprintf(&buf, "%s;\n", l.Text) + } + return buf.String() + } + + for _, test := range tests { + testName := filepath.Base(test.Name()) + testFile := filepath.Join("testdata", test.Name()) + t.Run(testName, func(t *testing.T) { + testCode, err := os.ReadFile(testFile) + if err != nil { + t.Fatal(err) + } + instList, err := assembler.CasmToBytecode(string(testCode)) + if err != nil { + t.Fatal(err) + } + disassembled, err := disasm.FromBytecode(disasm.Config{ + Bytecode: instList, + Indent: 0, + }) + if err != nil { + t.Fatal(err) + } + testCode2 := disasmProgToString(disassembled) + instList2, err := assembler.CasmToBytecode(testCode2) + if err != nil { + t.Fatalf("generated casm parse error: %v\nprog:\n%s", err, testCode2) + } + assert.Equal(t, instList, instList2) + if _, ok := checkTexts[testName]; ok { + assert.Equal(t, string(testCode), testCode2) + } + }) + } +} diff --git a/pkg/disasm/disassembler.go b/pkg/disasm/disassembler.go new file mode 100644 index 000000000..c0039778b --- /dev/null +++ b/pkg/disasm/disassembler.go @@ -0,0 +1,209 @@ +package disasm + +import ( + "fmt" + "strings" + + "github.com/NethermindEth/cairo-vm-go/pkg/assembler" + f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" +) + +type disassembler struct { + bytecode []*f.Element + + config Config + + instructions []casmInstruction + + labels map[int64]int + funcLabels map[int64]int + + formatter *casmFormatter + + prog Program +} + +func (d *disassembler) Disassemble() (*Program, error) { + type step struct { + name string + fn func() error + } + steps := []step{ + {"decode instructions", d.decodeInstructionsStep}, + {"collect labels", d.collectLabelsStep}, + {"construct program", d.constructProgramStep}, + } + for _, s := range steps { + if err := s.fn(); err != nil { + return nil, fmt.Errorf("%s: %w", s.name, err) + } + } + + return &d.prog, nil +} + +func (d *disassembler) decodeInstructionsStep() error { + offset := int64(0) + + for offset < int64(len(d.bytecode)) { + b := d.bytecode[offset] + decoded, err := assembler.DecodeInstruction(b) + if err != nil { + return fmt.Errorf("offset %d (%q): %w", offset, b.String(), err) + } + inst := casmInstruction{ + Instruction: decoded, + bytecodeOffset: offset, + } + if inst.Size() == 2 { + inst.arg = d.bytecode[offset+1] + } + d.instructions = append(d.instructions, inst) + offset += inst.Size() + } + + return nil +} + +func (d *disassembler) collectLabelsStep() error { + d.labels = make(map[int64]int) + d.funcLabels = make(map[int64]int) + + for _, inst := range d.instructions { + jumpTarget, ok := inst.JumpTarget() + if !ok { + continue + } + labels := d.labels + if inst.Opcode == assembler.OpCodeCall { + labels = d.funcLabels + } + if _, ok := labels[jumpTarget]; ok { + continue + } + id := len(labels) + labels[jumpTarget] = id + } + + return nil +} + +func (d *disassembler) constructProgramStep() error { + d.formatter = &casmFormatter{ + labels: d.labels, + funcLabels: d.funcLabels, + } + + for _, inst := range d.instructions { + funcLabelID, ok := d.funcLabels[inst.bytecodeOffset] + if ok { + d.pushCommentLine(" (pc=%d)", funcLabelID, inst.bytecodeOffset) + } + labelID, ok := d.labels[inst.bytecodeOffset] + if ok { + d.pushCommentLine(" (pc=%d)", labelID, inst.bytecodeOffset) + } + if err := d.pushDisasmLine(inst); err != nil { + return err + } + } + + return nil +} + +func (d *disassembler) pushDisasmLine(inst casmInstruction) error { + var buf strings.Builder + + buf.WriteString(strings.Repeat(" ", d.config.Indent)) + + if err := d.formatter.printInstruction(&buf, inst); err != nil { + return err + } + + comments, err := d.collectInstComments(inst) + if err != nil { + return err + } + + d.prog.Lines = append(d.prog.Lines, Line{ + Text: buf.String(), + Comments: comments, + }) + + return nil +} + +func (d *disassembler) pushCommentLine(format string, args ...any) { + s := fmt.Sprintf(format, args...) + d.prog.Lines = append(d.prog.Lines, Line{ + Comments: []string{s}, + }) +} + +func (d *disassembler) collectInstComments(inst casmInstruction) ([]string, error) { + var comments []string + + isAddApPseudo := false + + switch inst.Opcode { + case assembler.OpCodeCall: + jumpTarget, _ := inst.JumpTarget() + if funcLabelID, ok := d.funcLabels[jumpTarget]; ok { + comments = append(comments, fmt.Sprintf("calls F%d", funcLabelID)) + } + + case assembler.OpCodeAssertEq: + if inst.Op1Source == assembler.Imm { + // Try to recognize the division. + // So, instead of just this: + // > assert [fp+1] = [fp] * 2894802230932904970957858226476056084498485772265277359978473644908697616385 + // ...the user sees this (note the comment): + // > assert [fp+1] = [fp] * 2894802230932904970957858226476056084498485772265277359978473644908697616385 // div 5 + imm := inst.arg + if inst.Res == assembler.MulOperands && !imm.IsUint64() { + dividend := f.NewElement(0) + dividend.Inverse(imm) + // If divident is a very large number, then we could got it wrong. + if dividend.IsUint64() { + comments = append(comments, "div "+dividend.String()) + } + } + } + + case assembler.OpCodeNop: + jumpTarget, ok := inst.JumpTarget() + if ok { + if labelID, ok := d.labels[jumpTarget]; ok { + comments = append(comments, fmt.Sprintf("targets L%d", labelID)) + } + } + if inst.ApUpdate == assembler.AddRes && inst.Op1Source == assembler.Imm { + isAddApPseudo = true + } + + case assembler.OpCodeRet: + // Nothing to do. + + default: + return nil, fmt.Errorf("unexpected opcode: %v", inst.Opcode) + } + + switch inst.ApUpdate { + case assembler.Add2: + comments = append(comments, "ap += 2") + case assembler.AddRes: + if !isAddApPseudo { + if inst.Op1Source == assembler.Imm { + comments = append(comments, fmt.Sprintf("ap += %d", feltToInt64(inst.arg))) + } else { + comments = append(comments, "ap += $result") + } + } + case assembler.SameAp, assembler.Add1: + // Nothing to do. + default: + return nil, fmt.Errorf("unexpected ap update: %v", inst.ApUpdate) + } + + return comments, nil +} diff --git a/pkg/disasm/testdata/fib.casm b/pkg/disasm/testdata/fib.casm new file mode 100644 index 000000000..31cc648f9 --- /dev/null +++ b/pkg/disasm/testdata/fib.casm @@ -0,0 +1,8 @@ +jmp rel 4 if [fp + -3] != 0; +[ap + 0] = [fp + -5], ap++; +ret; +[ap + 0] = [fp + -4], ap++; +[ap + 0] = [fp + -5] + [fp + -4], ap++; +[fp + -3] = [ap + 0] + 1, ap++; +call rel -8; +ret; diff --git a/pkg/disasm/testdata/hash_chain.casm b/pkg/disasm/testdata/hash_chain.casm new file mode 100644 index 000000000..43e0964ec --- /dev/null +++ b/pkg/disasm/testdata/hash_chain.casm @@ -0,0 +1,12 @@ +jmp rel 6 if [fp + -3] != 0; +[ap + 0] = [fp + -4], ap++; +[ap + 0] = 0, ap++; +ret; +[ap + 0] = [fp + -4], ap++; +[fp + -3] = [ap + 0] + 1, ap++; +call rel -9; +[ap + -1] = [[ap + -2] + 0]; +[fp + -3] = [[ap + -2] + 1]; +[ap + 0] = [ap + -2] + 3, ap++; +[ap + 0] = [[ap + -3] + 2], ap++; +ret; diff --git a/pkg/disasm/testdata/hash_chain_pretty.casm b/pkg/disasm/testdata/hash_chain_pretty.casm new file mode 100644 index 000000000..5a58196a3 --- /dev/null +++ b/pkg/disasm/testdata/hash_chain_pretty.casm @@ -0,0 +1,12 @@ +jmp rel 6 if [fp-3] != 0; +[ap] = [fp-4], ap++; +[ap] = 0, ap++; +ret; +[ap] = [fp-4], ap++; +[fp-3] = [ap] + 1, ap++; +call rel -9; +[ap-1] = [[ap-2]]; +[fp-3] = [[ap-2]+1]; +[ap] = [ap-2] + 3, ap++; +[ap] = [[ap-3]+2], ap++; +ret; diff --git a/pkg/disasm/testdata/simple.casm b/pkg/disasm/testdata/simple.casm new file mode 100644 index 000000000..dbb68d353 --- /dev/null +++ b/pkg/disasm/testdata/simple.casm @@ -0,0 +1 @@ +ret; diff --git a/pkg/disasm/utils.go b/pkg/disasm/utils.go new file mode 100644 index 000000000..6167abdbd --- /dev/null +++ b/pkg/disasm/utils.go @@ -0,0 +1,24 @@ +package disasm + +import ( + "strconv" + + f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" +) + +func feltToInt64(felt *f.Element) int64 { + // This would not be correct: int64(felt.Uint64) + // since signed values will reside in more than one 64-bit word. + // + // BigInt().Int64() would not work neither. + // + // String() handles signed values pretty well for our use-case. + // Maybe there is another way to avoid the redundant String()+Parsing? + + s := felt.String() + v, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return 0 + } + return v +}