Zk sdk#953
Conversation
Easy conversion from risc0 proof to kaspa script!
|
Nice! Should we start investigating wasm bridging for r0 script builder? Since the SDK will mostly (ark seems no-std compat) depends on types (and no native calls?), it should be doable, what do you think? |
hey, yeah I think this is a good idea. let me check on this on my next pass on this |
| type Input = Vec<Vec<u8>>; | ||
|
|
||
| /// Deserialize an element over the G1 group from bytes in big-endian format | ||
| fn from_bytes(bytes: &Self::Input) -> Result<G1, PointError> { | ||
| if bytes.len() != 2 { | ||
| return Err(PointError::MalformedG1); | ||
| } | ||
| let g1_affine: Vec<u8> = bytes[0].iter().rev().chain(bytes[1].iter().rev()).cloned().collect(); |
There was a problem hiding this comment.
why shout it be 2 dimensional reversed vectors?
There was a problem hiding this comment.
r0 stores in BE order whilst ark expects LE, but I agree here that the fn name should be more specific to r0, not generic from_bytes
| if bytes.len() != 2 || bytes[0].len() != 2 || bytes[1].len() != 2 { | ||
| return Err(PointError::MalformedG2); | ||
| } | ||
| let g2_affine: Vec<u8> = bytes[0][1] | ||
| .iter() | ||
| .rev() | ||
| .chain(bytes[0][0].iter().rev()) | ||
| .chain(bytes[1][1].iter().rev()) | ||
| .chain(bytes[1][0].iter().rev()) | ||
| .cloned() | ||
| .collect(); |
There was a problem hiding this comment.
why shout it be 2 dimensional reversed vectors?
| fn to_fixed_array(input: &[u8]) -> [u8; 32] { | ||
| let mut fixed_array = [0u8; 32]; | ||
| let start = core::cmp::max(32, input.len()) - core::cmp::min(32, input.len()); | ||
| fixed_array[start..].copy_from_slice(&input[input.len().saturating_sub(32)..]); | ||
| fixed_array | ||
| } | ||
| impl R0ScriptBuilder { | ||
| /// Converts a Groth16Receipt into a Kaspa script. | ||
| /// This script unlocks the UTXO if the verification of the receipt | ||
| /// succeeds. | ||
| pub fn from_groth<Claim: Digestible + Clone>(receipt: &Groth16Receipt<Claim>) -> Result<ScriptBuilder> { | ||
| let mut params = Groth16ReceiptVerifierParameters::default(); | ||
| let seal = &receipt.seal; | ||
| let digested_claim = receipt.claim.digest::<sha::Impl>(); | ||
| let (a0, a1) = split_digest_bytes(params.control_root); | ||
| let (c0, c1) = split_digest_bytes(digested_claim); | ||
| let id_bn254 = to_fixed_array(params.bn254_control_id.as_bytes()); |
There was a problem hiding this comment.
digest impls Into into [u8;32]. method seems redundant
| pub fn try_verifying_key() -> Result<ark_groth16::VerifyingKey<Bn254>, R0Error> { | ||
| let alpha_g1 = G1::from_bytes(&vec![from_u256(ALPHA_X)?, from_u256(ALPHA_Y)?])?.0; | ||
| let beta_g2 = | ||
| G2::from_bytes(&vec![vec![from_u256(BETA_X1)?, from_u256(BETA_X2)?], vec![from_u256(BETA_Y1)?, from_u256(BETA_Y2)?]])?.0; | ||
| let gamma_g2 = | ||
| G2::from_bytes(&vec![vec![from_u256(GAMMA_X1)?, from_u256(GAMMA_X2)?], vec![from_u256(GAMMA_Y1)?, from_u256(GAMMA_Y2)?]])?.0; | ||
| let delta_g2 = | ||
| G2::from_bytes(&vec![vec![from_u256(DELTA_X1)?, from_u256(DELTA_X2)?], vec![from_u256(DELTA_Y1)?, from_u256(DELTA_Y2)?]])?.0; | ||
|
|
||
| let gamma_abc_g1 = vec![ | ||
| G1::from_bytes(&vec![from_u256(IC0_X)?, from_u256(IC0_Y)?])?.0, | ||
| G1::from_bytes(&vec![from_u256(IC1_X)?, from_u256(IC1_Y)?])?.0, | ||
| G1::from_bytes(&vec![from_u256(IC2_X)?, from_u256(IC2_Y)?])?.0, | ||
| G1::from_bytes(&vec![from_u256(IC3_X)?, from_u256(IC3_Y)?])?.0, | ||
| G1::from_bytes(&vec![from_u256(IC4_X)?, from_u256(IC4_Y)?])?.0, | ||
| G1::from_bytes(&vec![from_u256(IC5_X)?, from_u256(IC5_Y)?])?.0, | ||
| ]; | ||
|
|
||
| Ok(ark_groth16::VerifyingKey::<Bn254> { alpha_g1, beta_g2, gamma_g2, delta_g2, gamma_abc_g1 }) | ||
| } |
There was a problem hiding this comment.
the key is constant, why is it calculated in runtime. why does it require vector allocations and conversions from string
There was a problem hiding this comment.
Agreed, let me fix this
| pub fn try_verifying_key() -> Result<ark_groth16::VerifyingKey<Bn254>, R0Error> { | ||
| let alpha_g1 = G1::from_bytes(&vec![from_u256(ALPHA_X)?, from_u256(ALPHA_Y)?])?.0; | ||
| let beta_g2 = | ||
| G2::from_bytes(&vec![vec![from_u256(BETA_X1)?, from_u256(BETA_X2)?], vec![from_u256(BETA_Y1)?, from_u256(BETA_Y2)?]])?.0; | ||
| let gamma_g2 = | ||
| G2::from_bytes(&vec![vec![from_u256(GAMMA_X1)?, from_u256(GAMMA_X2)?], vec![from_u256(GAMMA_Y1)?, from_u256(GAMMA_Y2)?]])?.0; | ||
| let delta_g2 = | ||
| G2::from_bytes(&vec![vec![from_u256(DELTA_X1)?, from_u256(DELTA_X2)?], vec![from_u256(DELTA_Y1)?, from_u256(DELTA_Y2)?]])?.0; | ||
|
|
||
| let gamma_abc_g1 = vec![ | ||
| G1::from_bytes(&vec![from_u256(IC0_X)?, from_u256(IC0_Y)?])?.0, | ||
| G1::from_bytes(&vec![from_u256(IC1_X)?, from_u256(IC1_Y)?])?.0, | ||
| G1::from_bytes(&vec![from_u256(IC2_X)?, from_u256(IC2_Y)?])?.0, | ||
| G1::from_bytes(&vec![from_u256(IC3_X)?, from_u256(IC3_Y)?])?.0, | ||
| G1::from_bytes(&vec![from_u256(IC4_X)?, from_u256(IC4_Y)?])?.0, | ||
| G1::from_bytes(&vec![from_u256(IC5_X)?, from_u256(IC5_Y)?])?.0, | ||
| ]; | ||
|
|
||
| Ok(ark_groth16::VerifyingKey::<Bn254> { alpha_g1, beta_g2, gamma_g2, delta_g2, gamma_abc_g1 }) | ||
| } |
There was a problem hiding this comment.
there is no test that ensures its the same calculatuon of verifying key as upstream crate has
There was a problem hiding this comment.
There is no way to extract inner verifying key from r0 repo its only public to the crate see: pub struct VerifyingKey(pub(crate) VerifyingKey<Bn<Config>>) in risc0_groth16::verifier
There was a problem hiding this comment.
There is an indirect test, there is an r0 proof that verifies using this key.
There was a problem hiding this comment.
But ill make it better
| } | ||
| } | ||
|
|
||
| impl TryFrom<&String> for HashFnId { |
| } | ||
| } | ||
|
|
||
| impl TryFrom<&String> for HashFnId { |
There was a problem hiding this comment.
fromStr seems a better trait candidate than tryfrom
There was a problem hiding this comment.
why what if you pass incorrect string? then everything will panic
Switch Groth16 handling to use a static R0 serialized uncompressed verifying key and r0-specific byte deserialization. Rename PointFromBytes::from_bytes to from_r0_bytes and update callers; replace runtime VK construction with R0_SERIALIZED_UNCOMPRESSED_VK and deserialize_uncompressed. Simplify digest/id conversions and remove legacy fixed-array helper. Add num-bigint parse error variant and adjust HashFnId::TryFrom to accept &str. Remove unused result module and clean up unused imports. Also add workspace entries for hex, bincode and num-bigint and add a test to validate upstream VK serialization parity.
|
There are some elements which I have identified to be missing, most importantly the generation of safe bounded Groth16 script by image id. |
Introduce a builder API and wiring to convert RISC0 receipts into Kaspa scripts. Added zk_to_script builder modules (bind, finalize, tag, proof with groth16 and succinct backends) and a stateful R0ScriptBuilder type. Implemented Groth16 support: serializing VK/proofs, static R0 VK bytes, append_locking_groth16 and append_spending_groth16 helpers, and digest-splitting for BN254 compatibility. Added succinct proof handling to pack seal, control proof, and journal into scripts. Updated groth16 locking logic to recompute claim/output digests and assemble precompile inputs. Expanded tests to exercise new builders and script assembly. Also modified rcpt.rs to emit a debug hex of a potential control id (println present).
Add new zk_to_script builder backends (commit/groth16, commit/succinct, and related proof modules), update builder module wiring and Groth16 VK handling, and adjust rcpt handling and tests (updated image/rcpt hex fixtures). Remove several legacy/unused riscv builder files (converter, bind, finalize, tag, some groth16 modules). Also update Cargo.lock to reflect added/updated dependencies required for risc0/ark groth16 integration and other dependency bumps. These changes enable risc0 groth16/succinct zk-to-script support and pin the corresponding crate versions.
| edition = "2024" | ||
| include = [ | ||
| "src/**/*.rs", | ||
| "src/*risc0-zkvm.workspace*/*.rs", |
There was a problem hiding this comment.
is this intentional or a leftover? why does that path need special treatment here?
There was a problem hiding this comment.
You are right must be a leftover, fixing.
This PR implements the beginning of a ZK sdk. The goal with this SDK is to reduce the knowledge requirement factor when an actor needs to deal with UTXO based ZK systems that are largely non-existent in the same relative scale as others.
The sdk allows one to convert any R0 proof whether this is a SuccinctReceipt or Groth16Receipt into a native Kaspa script, reducing the workload for protocols to migrate onto Kaspa and barrier for adoption.