Skip to content

Commit 6a44180

Browse files
committed
fix(zkcc): use interleaved schema ordering matching native witness layout
The BIP340 schema (make_bip340_schema) built private entries grouped by array type (all bits_s, then all int_sx, etc.), but the native witness in lfzk_bindings.cc is interleaved per iteration: bits_s[i], int_sx[i], int_sy[i], int_sz[i]. This mismatch caused named-input resolution via build_witness_inputs to assign wrong indices, making proof generation fail with "eval_circuit failed". Additionally, private entry indices were relative to the private sub-table (starting at 1), colliding with public input indices. Fix: interleaved ordering with absolute 1-based indices matching v_[1..N] in the dense witness array. Add regression test proving vector 0 through build_witness_inputs with named keys, exercising the interleaved schema mapping path.
1 parent 579e816 commit 6a44180

2 files changed

Lines changed: 125 additions & 38 deletions

File tree

src/lua/crypto_zkcc.lua

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -631,51 +631,63 @@ end
631631

632632
M.named_logic = new_named_logic
633633

634-
local function repeat_named_entries(entries, prefix, count, kind, input_type)
635-
for i = 1, count do
636-
entries[#entries + 1] = {
634+
local function make_bip340_schema(artifact)
635+
-- Builds the schema for the native BIP340 verification circuit.
636+
-- Entries are ordered and indexed to match Bip340Verify::Witness::input()
637+
-- in lfzk_bindings.cc: interleaved bits_s[i], int_sx[i], int_sy[i],
638+
-- int_sz[i] (one iteration per i), then e·P trace (also interleaved),
639+
-- then py, ry, rz_inv, bits_ry.
640+
-- Indices are 1-based absolute positions in the dense witness array.
641+
642+
local public = {}
643+
local private = {}
644+
local full = {}
645+
local seq = 1 -- 1-based absolute index; v_[0] is the constant-1
646+
647+
local function add(list, name, kind, input_type)
648+
list[#list + 1] = {
637649
kind = kind,
638-
name = string.format("%s_%03d", prefix, i),
650+
name = name,
639651
type = input_type,
640-
desc = prefix,
641-
index = #entries + 1,
642-
decl_order = #entries + 1,
652+
desc = name,
653+
index = seq,
654+
decl_order = seq,
643655
}
656+
seq = seq + 1
644657
end
645-
end
646658

647-
local function append_named_entry(entries, name, kind, input_type)
648-
entries[#entries + 1] = {
649-
kind = kind,
650-
name = name,
651-
type = input_type,
652-
desc = name,
653-
index = #entries + 1,
654-
decl_order = #entries + 1,
655-
}
656-
end
659+
-- Public inputs: v_[1..3]
660+
add(public, "rx", "public", "field")
661+
add(public, "px", "public", "field")
662+
add(public, "e", "public", "field")
657663

658-
local function make_bip340_schema(artifact)
659-
local public = {}
660-
local private = {}
661-
local full = {}
664+
-- Private: s·G trace (interleaved)
665+
for i = 1, 256 do
666+
add(private, string.format("bits_s_%03d", i), "private", "field")
667+
if i < 256 then
668+
add(private, string.format("int_sx_%03d", i), "private", "field")
669+
add(private, string.format("int_sy_%03d", i), "private", "field")
670+
add(private, string.format("int_sz_%03d", i), "private", "field")
671+
end
672+
end
662673

663-
append_named_entry(public, "rx", "public", "field")
664-
append_named_entry(public, "px", "public", "field")
665-
append_named_entry(public, "e", "public", "field")
666-
667-
repeat_named_entries(private, "bits_s", 256, "private", "field")
668-
repeat_named_entries(private, "int_sx", 255, "private", "field")
669-
repeat_named_entries(private, "int_sy", 255, "private", "field")
670-
repeat_named_entries(private, "int_sz", 255, "private", "field")
671-
repeat_named_entries(private, "bits_e", 256, "private", "field")
672-
repeat_named_entries(private, "int_ex", 255, "private", "field")
673-
repeat_named_entries(private, "int_ey", 255, "private", "field")
674-
repeat_named_entries(private, "int_ez", 255, "private", "field")
675-
append_named_entry(private, "py", "private", "field")
676-
append_named_entry(private, "ry", "private", "field")
677-
append_named_entry(private, "rz_inv", "private", "field")
678-
repeat_named_entries(private, "bits_ry", 256, "private", "field")
674+
-- Private: e·P trace (interleaved)
675+
for i = 1, 256 do
676+
add(private, string.format("bits_e_%03d", i), "private", "field")
677+
if i < 256 then
678+
add(private, string.format("int_ex_%03d", i), "private", "field")
679+
add(private, string.format("int_ey_%03d", i), "private", "field")
680+
add(private, string.format("int_ez_%03d", i), "private", "field")
681+
end
682+
end
683+
684+
add(private, "py", "private", "field")
685+
add(private, "ry", "private", "field")
686+
add(private, "rz_inv", "private", "field")
687+
688+
for i = 1, 256 do
689+
add(private, string.format("bits_ry_%03d", i), "private", "field")
690+
end
679691

680692
local schema = {
681693
public = public,

test/lua/zkcc_bip340.lua

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,81 @@ local loaded = zkcc.load_circuit_artifact_bip340(circuit_bytes)
146146
print(string.format("Circuit size: %d bytes, inputs=%d, public=%d",
147147
#circuit_bytes, circuit.ninput - 1, circuit.npub_input - 1))
148148

149+
-- ===========================================================================
150+
-- Regression: prove vector 0 via build_witness_inputs with named keys.
151+
-- This exercises the interleaved schema mapping path (fix for named-input
152+
-- ordering: schema must match Bip340Verify::Witness::input() in
153+
-- lfzk_bindings.cc).
154+
-- ===========================================================================
155+
print("=== Named-input regression test (vector 0) ===")
156+
157+
local sig0 = OCTET.from_hex("E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0")
158+
local pk0 = OCTET.from_hex("F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9")
159+
local msg0 = OCTET.from_hex("0000000000000000000000000000000000000000000000000000000000000000")
160+
local seed0 = OCTET.from_hex("0000000000000000000000000000000000000000000000000000000000000001")
161+
162+
-- Obtain field-element OCTETs from bip340_compute (direct call, no pcall)
163+
local w = zkcc.witness.bip340_compute(sig0, pk0, msg0)
164+
165+
-- Build a named-inputs table matching the interleaved schema order
166+
local named = {
167+
rx = w.rx,
168+
px = w.px,
169+
e = w.e,
170+
}
171+
for i = 1, 256 do
172+
named[string.format("bits_s_%03d", i)] = w.bits_s[i]
173+
if i < 256 then
174+
named[string.format("int_sx_%03d", i)] = w.int_sx[i]
175+
named[string.format("int_sy_%03d", i)] = w.int_sy[i]
176+
named[string.format("int_sz_%03d", i)] = w.int_sz[i]
177+
end
178+
end
179+
for i = 1, 256 do
180+
named[string.format("bits_e_%03d", i)] = w.bits_e[i]
181+
if i < 256 then
182+
named[string.format("int_ex_%03d", i)] = w.int_ex[i]
183+
named[string.format("int_ey_%03d", i)] = w.int_ey[i]
184+
named[string.format("int_ez_%03d", i)] = w.int_ez[i]
185+
end
186+
end
187+
named.py = w.py
188+
named.ry = w.ry
189+
named.rz_inv = w.rz_inv
190+
for i = 1, 256 do
191+
named[string.format("bits_ry_%03d", i)] = w.bits_ry[i]
192+
end
193+
194+
-- Build witness inputs from named keys
195+
local named_inputs = zkcc.build_witness_inputs{
196+
circuit = loaded,
197+
inputs = named,
198+
}
199+
local named_public = zkcc.build_witness_inputs{
200+
circuit = loaded,
201+
public_inputs = { rx = w.rx, px = w.px, e = w.e },
202+
}
203+
204+
-- Prove and verify through the named path
205+
local named_proof = zkcc.prove_circuit{
206+
circuit = loaded,
207+
inputs = named_inputs,
208+
seed = seed0,
209+
}
210+
assert(named_proof and #named_proof > 0, "named-input proof generation failed")
211+
212+
local named_ok = zkcc.verify_circuit{
213+
circuit = loaded,
214+
proof = named_proof,
215+
public_inputs = named_public,
216+
seed = seed0,
217+
}
218+
assert(named_ok, "named-input verification failed")
219+
print(" named-input prove+verify: ok")
220+
221+
-- ===========================================================================
222+
-- Main BIP340 vector sweep
223+
-- ===========================================================================
149224
local vectors = load_vectors()
150225
assert(#vectors > 0, "missing BIP340 test vectors")
151226

0 commit comments

Comments
 (0)