Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
884c5ad
spec/EC: introduce ECSM
erik-3milabs May 29, 2026
1c274bd
spec/ECSM: introduce EC_SCALAR
erik-3milabs Jun 2, 2026
d2d08d8
spec/ECSM: introduce ECDAS
erik-3milabs Jun 3, 2026
3a46d5a
spec/ECSM: two carry proofs
erik-3milabs Jun 3, 2026
bd64b62
spec/ECSM: update lemma
erik-3milabs Jun 4, 2026
d098975
spec/ECSM: carry computation tool
erik-3milabs Jun 4, 2026
47a5be3
spec/ECSM: fix lint issues
erik-3milabs Jun 5, 2026
b9530a9
spec/ECSM: update extension names
erik-3milabs Jun 5, 2026
5ae2283
spec/ECSM: update introduction
erik-3milabs Jun 5, 2026
e3848b5
Spec/ECSM: update ecsm to work with offsets
erik-3milabs Jun 5, 2026
fe4e3dd
spec/ECDAS: update non-inf argumentation
erik-3milabs Jun 8, 2026
7e12156
spec: fix cwrap for repeated subtractions
erik-3milabs Jun 8, 2026
7988057
spec/ECSM: tweaks and fixes
erik-3milabs Jun 9, 2026
6db8da4
spec/ECSM: fix scalar read/serve timestamp
erik-3milabs Jun 9, 2026
aab3904
spec/ecsm: more tweaks and fixes
erik-3milabs Jun 9, 2026
0a9d87e
spec/ECSM: update carries
erik-3milabs Jun 9, 2026
d9a1c3e
spec/ECSM: drop carry.py
erik-3milabs Jun 9, 2026
33d65ea
Apply suggestions from code review
erik-3milabs Jun 9, 2026
bac746b
Update spec/ecsm.typ
erik-3milabs Jun 9, 2026
f604cd5
spec/ECSM: fix incorrect carry index
erik-3milabs Jun 9, 2026
18a815a
spec/ECSM: fix recursive `SERVE_K` multiplicity
erik-3milabs Jun 9, 2026
d7cefb2
spec/ECSM: define padding
erik-3milabs Jun 9, 2026
1b4a50d
spec/ECSM: fix missing trailing emptyline
erik-3milabs Jun 9, 2026
1764697
spec/ECDAS: fix missing multiplicity range check
erik-3milabs Jun 11, 2026
93c7eb0
spec/ECDAS: fix BIT-interaction multiplicity negation
erik-3milabs Jun 11, 2026
33e846e
spec/ECSM: fix padding bug
erik-3milabs Jun 11, 2026
ab4d3de
spec/ECDAS: range-check `op`
erik-3milabs Jun 12, 2026
1bf0b01
spec/ECSM: invert BIT-multiplicities
erik-3milabs Jun 12, 2026
10f7657
spec/ECSM: rename `len_k` as `idx_k`
erik-3milabs Jun 12, 2026
594c17b
spec/ECSM: update ECALL-number
erik-3milabs Jun 12, 2026
ba299d4
spec/ECSM: update accelerator's interval for p
erik-3milabs Jun 12, 2026
6e2d99b
spec/ECSM: fix typo
erik-3milabs Jun 12, 2026
601b681
spec/ECSM: update no-inf clarification
erik-3milabs Jun 12, 2026
af3818e
spec/ECDAS: codify assumptions
erik-3milabs Jun 12, 2026
0c13fad
spec/ECSM: remove invalid x_G
erik-3milabs Jun 12, 2026
717bfc6
spec/ECMS: generalize impossible y=0 argument
erik-3milabs Jun 12, 2026
845b550
spec/ECSM: extract carry magic numbers as constants.
erik-3milabs Jun 12, 2026
3a90432
spec/ECSM: fix quotient bound off-by-fews
erik-3milabs Jun 16, 2026
674b098
spec/ECSM: fix unclear division notation
erik-3milabs Jun 16, 2026
958f274
spec/ECSM: update ECDAS mathematical overview
erik-3milabs Jun 16, 2026
0e7dffc
spec: introduce attention-block
erik-3milabs Jun 16, 2026
2ff2ac0
spec/ECSM: update background and overview
erik-3milabs Jun 16, 2026
f78f9d7
spec: fix lint-breaking typo
erik-3milabs Jun 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion spec/about_ecalls.typ
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ Negative numbers (represented as 2s complement 64-bit numbers), are used for our
/ 64: `write` (@commit)
/ 93: `exit` (@halt)
/ -1: `SHA256` (@sha256)
/ -2: `KECCAK` (@keccak)
/ -2: `KECCAK` (@keccak)
/ -11: `ECSM` (@ecsm)
9 changes: 7 additions & 2 deletions spec/book.typ
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
("commit.typ", [`COMMIT` chip], <commit>),
("sha256.typ", [`SHA256` accelerator], <sha256>),
("keccak.typ", [`KECCAK` accelerator], <keccak>),
("ecsm.typ", [`ECSM` accelerator], <ecsm>),
))
)
)
Expand Down Expand Up @@ -79,16 +80,20 @@
#let et = todo.with(background: rgb("d4aa3a"), name: "Erik")
#let cdsg = todo.with(background: olive, name: "Cyprien")

#let aside(title, body) = context figure(
#let highlight(title, body, color) = context figure(
block(inset: (left: 1em, right: 1em, bottom: 1em), stroke: luma(50%), breakable: false)[
#block(inset: (left: 1em, right: 1em, top: .75em, bottom: .75em),
width: 100% + 2em,
fill: rgb("55aaff"),
fill: color,
stroke: luma(50%),
align(center, strong(text(fill: black, title))))
#align(left, body)
])

#let aside(title, body) = highlight(title, body, rgb("55aaff"))

#let attention(title, body) = highlight("Attention: " + title, body, rgb("#ff2600"))


#let is-shiroa = "x-target" in sys.inputs

Expand Down
429 changes: 429 additions & 0 deletions spec/ecsm.typ

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion spec/expr.typ
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
cwrap(`-` + rec(PREC.neg, e.at(1)), pp < PREC.neg)
} else {
// Subtraction
cwrap(e.slice(1).map(rec.with(PREC.sub)).join(` - `), pp < PREC.sub)
cwrap(e.slice(1).map(rec.with(PREC.sub)).join(` - `), pp <= PREC.sub)
}
},
"cast": (pp, rec, e) => {
Expand Down
32 changes: 32 additions & 0 deletions spec/src/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,38 @@ desc = """\
Represented as an array of four `Word` variables.\
"""

[[variables.types]]
label = "U256BL"
subtypes = ["Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte"]
desc = """\
Variable that can only assume values in the range $[0, 2^256)$. \\
Represented as an array of thirty-two `Byte` variables.\
"""

[[variables.types]]
label = "U256HL"
subtypes = ["Half", "Half", "Half", "Half", "Half", "Half", "Half", "Half", "Half", "Half", "Half", "Half", "Half", "Half", "Half", "Half"]
desc = """\
Variable that can only assume values in the range $[0, 2^256)$. \\
Represented as an array of sixteen `Half` variables.\
"""

[[variables.types]]
label = "U256WL"
subtypes = ["Word", "Word", "Word", "Word", "Word", "Word", "Word", "Word"]
desc = """\
Variable that can only assume values in the range $[0, 2^256)$. \\
Represented as an array of eight `Word` variables.\
"""

[[variables.types]]
label = "U512BL"
subtypes = ["Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte"]
desc = """\
Variable that can only assume values in the range $[0, 2^512)$. \\
Represented as an array of sixty-four `Byte` variables.\
"""

[[variables.types]]
label = "Timestamp"
subtypes = ["DWordWL"]
Expand Down
117 changes: 117 additions & 0 deletions spec/src/ec_scalar.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name = "EC_SCALAR"

[[variables.input]]
name = "timestamp"
type = "DWordWL"
desc = "timestamp at which to serve the constant"
pad = 0

[[variables.input]]
name = "ptr"
type = "DWordWL"
desc = "pointer to the first byte of the scalar"
pad = 0
Comment on lines +9 to +13

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we doing a bunch of MEMWs in this chip? The calling ECSM has already read all of K into byte limbs, so we can just take in the limb as input and decompose it, right?

I guess the reason is that EC_SCALAR recurses onto itself, reducing the interactions in the ECSM table, but I think for total area it'd be better feed in the bytes directly; at which point, there may be some tradeoff between how many bits EC_SCALAR does at once for width vs depth, but again probably total area tells us to just make it 256 wide?
In the case 256 columns wide wouldn't be the right call, just making multiple calls into EC_SCALAR from ECSM should nevertheless still be the same area cost as the recursion, but simpler because there's no recursion logic to be performed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may even be some approach where we avoid the recursion of ECDAS as well, and simply let the EC_SCALAR chip emit the sequence of double/add steps that need to be performed. Not entirely sure if that wouldn't introduce too many more interactions atm, but it's potentially cleaner than having to reason about the recursion stopping.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we doing a bunch of MEMWs in this chip? The calling ECSM has already read all of K into byte limbs, so we can just take in the limb as input and decompose it, right?
I guess the reason is that EC_SCALAR recurses onto itself, reducing the interactions in the ECSM table, but I think for total area it'd be better feed in the bytes directly;

I ran the numbers, and you're right: switching would save 32 interactions and 160 cells per ECSM-call.

at which point, there may be some tradeoff between how many bits EC_SCALAR does at once for width vs depth, but again probably total area tells us to just make it 256 wide?
In the case 256 columns wide wouldn't be the right call, just making multiple calls into EC_SCALAR from ECSM should nevertheless still be the same area cost as the recursion, but simpler because there's no recursion logic to be performed.

You're right: Making EC-SCALAR work on all 256 bits at once would reduce the cell-footprint by another ~15% (from 320 to 257).

In fact, we can go even one step further, and merge EC-SCALAR into ECSM. This would save 2 interactions (the transfer of k from ECSM to EC-SCALAR, on both chips) and 33 cells (= multiplicity on EC-SCALAR + the byte representation of k on ECSM can be virtualized from the individual bits).

The thing I keep returning to, is whether padding isn't screwing with minimizing total area as a heuristic. I'll spend some time this afternoon to investigate this for a bit.


[[variables.input]]
name = "offset"
type = "Byte"
desc = "index of limb"
pad = 0

[[variables.auxiliary]]
name = "limb_bits"
type = ["Bit", 8]
desc = "bit-decomposition of the limb being read"
pad = ["arr", 0, 0, 0, 0, 0, 0, 0, 0]

[[variables.auxiliary]]
name = "last_limb"
type = "Bit"
desc = "whether this is the last limb to read"
pad = 0

[[variables.virtual]]
name = "limb"
type = "Byte"
desc = "limb being read"
def = {poly=["sum", ["=", "i", 0], 7, ["*", ["^", 2, "i"], ["idx", "limb_bits", "i"]]]}

[[variables.multiplicity]]
name = "μ"
type = "Bit"
desc = ""
pad = 0

[[assumptions]]
desc = "$#`ptr` + #`offset`$ does not overflow the bottom limb"

[[constraint_groups]]
name = "recv"

[[constraints.recv]]
kind = "interaction"
tag = "SERVE_K"
input = ["timestamp", "ptr", "offset"]
multiplicity = ["-", "μ"]

[[constraints.recv]]
kind = "template"
tag = "IS_BIT"
input = ["μ"]

[[constraint_groups]]
name = "read"

[[constraints.read]]
kind = "interaction"
tag = "MEMW"
input = [0, ["+", "ptr", ["arr", ["cast", "offset", "Word"], 0]], ["arr", "limb", 0, 0, 0, 0, 0, 0, 0], ["+", "timestamp", ["cast", 1, "DWordWL"]], 0, 0, 0]
output = ["arr", "limb", 0, 0, 0, 0, 0, 0, 0]
multiplicity = "μ"

[[constraints.read]]
kind = "template"
tag = "IS_BIT"
input = [["idx", "limb_bits", "i"]]
iter = ["i", 0, 7]


[[constraint_groups]]
name = "serve"

[[constraints.serve]]
kind = "interaction"
tag = "BIT"
input = ["timestamp", ["+", ["*", 8, "offset"], "i"]]
iter = ["i", 0, 7]
multiplicity = ["-", ["idx", "limb_bits", "i"]]

[[constraints.serve]]
kind = "arith"
constraint = "$#`limb_bits`_i = 1 => #`μ` = 1$"
poly = ["*", ["idx", "limb_bits", "i"], ["not", "μ"]]
iter = ["i", 0, 7]

[[constraint_groups]]
name = "recurse"

[[constraints.recurse]]
kind = "template"
tag = "IS_BIT"
input = ["last_limb"]

[[constraints.recurse]]
kind = "arith"
constraint = "$#`last_limb` = 1 => #`μ` = 1$"
poly = ["*", "last_limb", ["not", "μ"]]

[[constraints.recurse]]
kind = "arith"
constraint = "$#`last_limb` = 1 => #`offset` = 0$"
poly = ["*", "last_limb", "offset"]

[[constraints.recurse]]
kind = "interaction"
tag = "SERVE_K"
input = ["timestamp", "ptr", ["-", "offset", 1]]
multiplicity = ["-", "μ", "last_limb"]
Loading
Loading