risc0 icon indicating copy to clipboard operation
risc0 copied to clipboard

[BUG] 'failed to fill whole buffer' error when using k256 crate

Open conduition opened this issue 1 year ago • 11 comments

Bug Report

When using the k256 crate - a pure Rust secp256k1 implementation - in the zkVM guest, the guest program executes OK, but prover.prove returns an error on the host side: failed to fill whole buffer

$ cargo run .
proving execution...
thread 'main' panicked at host/src/main.rs:28:10:
failed to prove: failed to fill whole buffer
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Following the backtrace, the error seems to originate from the ark-std crate, in src/io/mod.rs. I am currently waiting for risc0 to reinstall. Once it does I will provide a full backtrace.

If I comment out the two critical lines in the guest crate's code, the proof runs OK, except obviously the elliptic curve point output is not written to the journal anymore.

diff --git a/methods/guest/src/main.rs b/methods/guest/src/main.rs
index 78e7b81..a2a905f 100644
--- a/methods/guest/src/main.rs
+++ b/methods/guest/src/main.rs
@@ -25,8 +25,8 @@ fn main() {
 
     // Compute the public key of the given input.
     // Uncommenting these lines out will cause the host to panic.
-    let point = secp256k1_ec_mul(&input);
-    output[32..].copy_from_slice(&point);
+    // let point = secp256k1_ec_mul(&input);
+    // output[32..].copy_from_slice(&point);
 
     let output_bytes: &[u8] = output.as_ref();
     env::commit(&output_bytes);
$ cargo run .
proving execution...
receipt journal output:
  hash:  72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793
  point: 000000000000000000000000000000000000000000000000000000000000000000
verifying receipt...
OK

Steps to Reproduce

  1. I followed the instructions here to install the risc0 tools and toolchain.
  2. I set up a simple ZK key-statement proof which asserts than a given hash preimage is also the secret key to some public key on the secp256k1 curve. The full code is here, but i'll also include the guest and host code below.

Guest

#![no_main]
#![no_std]

use k256::elliptic_curve::group::GroupEncoding as _;
use risc0_zkvm::guest::env;
use risc0_zkvm::sha;
use sha::Sha256 as _;

risc0_zkvm::guest::entry!(main);

#[allow(dead_code)]
fn secp256k1_ec_mul(secret: &[u8; 32]) -> [u8; 33] {
    let seckey = k256::SecretKey::from_bytes(secret.into()).expect("secret key is invalid");
    let pubkey = seckey.public_key();
    pubkey.as_affine().to_bytes().into()
}

fn main() {
    let input: [u8; 32] = env::read();
    let mut output = [0u8; 32 + 33];

    // Compute SHA256 hash, and write it to the journal as a public output
    let digest = sha::Impl::hash_bytes(&input);
    output[..32].copy_from_slice(digest.as_bytes());

    // Compute the public key of the given input.
    // Uncommenting these lines out will cause the host to panic.
    let point = secp256k1_ec_mul(&input);
    output[32..].copy_from_slice(&point);

    let output_bytes: &[u8] = output.as_ref();
    env::commit(&output_bytes);
}

Cargo.toml:

[package]
name = "provable_sha256_dlog"
version = "0.1.0"
edition = "2021"

[workspace]

[dependencies]
# If you want to try (experimental) std support, add `features = [ "std" ]` to risc0-zkvm
risc0-zkvm = { version = "1.0.1", default-features = false, features = [] }
k256 = { version = "0.13.3", default-features = false, features = ["arithmetic", "serde"] }

Host

// These constants represent the RISC-V ELF and the image ID generated by risc0-build.
// The ELF is used for proving and the ID is used for verification.
use methods::{PROVABLE_SHA256_DLOG_ELF, PROVABLE_SHA256_DLOG_ID};
use risc0_zkvm::{default_prover, ExecutorEnv};

fn main() {
    // Initialize tracing. In order to view logs, run `RUST_LOG=info cargo run`
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env())
        .init();

    // Arbitrary input.
    let input = [1u8; 32];

    let env = ExecutorEnv::builder()
        .write(&input)
        .unwrap()
        .build()
        .unwrap();

    // Obtain the default prover.
    let prover = default_prover();

    // Produce a receipt by proving the specified ELF binary.
    println!("proving execution...");
    let receipt = prover
        .prove(env, PROVABLE_SHA256_DLOG_ELF)
        .expect("failed to prove"); // <-------- CRASH HERE

    // Parse the journal from a receipt.
    let output: Vec<u8> = receipt
        .receipt
        .journal
        .decode()
        .expect("failed to decode journal output");

    assert_eq!(output.len(), 32 + 33);

    let hash = &output[..32];
    let point = &output[32..];

    println!("receipt journal output:");
    println!("  hash:  {}", hex::encode(hash));
    println!("  point: {}", hex::encode(point));

    // The receipt was verified at the end of proving, but the below code is an
    // example of how someone else could verify this receipt.
    println!("verifying receipt...");
    receipt.receipt.verify(PROVABLE_SHA256_DLOG_ID).unwrap();
    println!("OK");
}

Cargo.toml

[package]
name = "host"
version = "0.1.0"
edition = "2021"

[dependencies]
methods = { path = "../methods" }
risc0-zkvm = { version = "1.0.1" }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
serde = "1.0"
hex = { version = "0.4.3", default-features = false }
  1. cargo run . -> Panics.

Expected behavior

If the k256 elliptic curve code should work with zkVM, the proof should be constructed successfully.

If k256 is doing something wrong/disallowed by the zkVM, I should see some clear error telling me what/why so that I can help address it upstream.

Your Environment

  • risc0-zkvm version: 0.20.1
  • Rust version: rustc 1.76.0 (07dca489a 2024-02-04)
  • Platform/OS: Linux (Debian 12)

Additional context

TODO:

  • Does the prover work in dev mode?
  • Does the prover work if no elliptic curve math is done, but k256 is still used? What if we only do finite field math (e.g. multiply the input secret) instead of point-scalar multiplication?

conduition avatar Feb 18 '24 04:02 conduition

The full backtrace:

$ RUST_BACKTRACE=full cargo run .
proving execution...
thread 'main' panicked at host/src/main.rs:28:10:
failed to prove: failed to fill whole buffer

Stack backtrace:
   0: anyhow::error::<impl core::convert::From<E> for anyhow::Error>::from
             at /home/user/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anyhow-1.0.79/src/error.rs:565:25
   1: <core::result::Result<T,F> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible,E>>>::from_residual
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/result.rs:1959:27
   2: risc0_zkvm::host::api::ConnectionWrapper::recv
   3: risc0_zkvm::host::api::client::Client::prove_handler
             at /home/user/.cargo/registry/src/index.crates.io-6f17d22bba15001f/risc0-zkvm-0.20.1/src/host/api/client.rs:510:47
   4: risc0_zkvm::host::api::client::Client::prove
             at /home/user/.cargo/registry/src/index.crates.io-6f17d22bba15001f/risc0-zkvm-0.20.1/src/host/api/client.rs:87:21
   5: <risc0_zkvm::host::client::prove::external::ExternalProver as risc0_zkvm::host::client::prove::Prover>::prove_with_ctx
             at /home/user/.cargo/registry/src/index.crates.io-6f17d22bba15001f/risc0-zkvm-0.20.1/src/host/client/prove/external.rs:55:23
   6: risc0_zkvm::host::client::prove::Prover::prove
             at /home/user/.cargo/registry/src/index.crates.io-6f17d22bba15001f/risc0-zkvm-0.20.1/src/host/client/prove/mod.rs:65:9
   7: host::main
             at ./host/src/main.rs:26:19
   8: core::ops::function::FnOnce::call_once
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/ops/function.rs:250:5
   9: std::sys_common::backtrace::__rust_begin_short_backtrace
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:155:18
  10: std::rt::lang_start::{{closure}}
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/rt.rs:166:18
  11: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/ops/function.rs:284:13
  12: std::panicking::try::do_call
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:552:40
  13: std::panicking::try
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:516:19
  14: std::panic::catch_unwind
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panic.rs:142:14
  15: std::rt::lang_start_internal::{{closure}}
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/rt.rs:148:48
  16: std::panicking::try::do_call
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:552:40
  17: std::panicking::try
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:516:19
  18: std::panic::catch_unwind
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panic.rs:142:14
  19: std::rt::lang_start_internal
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/rt.rs:148:20
  20: main
  21: __libc_start_main
  22: _start
stack backtrace:
   0:     0x607063810686 - std::backtrace_rs::backtrace::libunwind::trace::hbee8a7973eeb6c93
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/../../backtrace/src/backtrace/libunwind.rs:104:5
   1:     0x607063810686 - std::backtrace_rs::backtrace::trace_unsynchronized::hc8ac75eea3aa6899
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
   2:     0x607063810686 - std::sys_common::backtrace::_print_fmt::hc7f3e3b5298b1083
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:68:5
   3:     0x607063810686 - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::hbb235daedd7c6190
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:44:22
   4:     0x60706383d000 - core::fmt::rt::Argument::fmt::h76c38a80d925a410
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/fmt/rt.rs:142:9
   5:     0x60706383d000 - core::fmt::write::h3ed6aeaa977c8e45
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/fmt/mod.rs:1120:17
   6:     0x60706380d95f - std::io::Write::write_fmt::h78b18af5775fedb5
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/io/mod.rs:1810:15
   7:     0x607063810464 - std::sys_common::backtrace::_print::h5d645a07e0fcfdbb
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:47:5
   8:     0x607063810464 - std::sys_common::backtrace::print::h85035a511aafe7a8
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:34:9
   9:     0x607063811b37 - std::panicking::default_hook::{{closure}}::hcce8cea212785a25
  10:     0x607063811899 - std::panicking::default_hook::hf5fcb0f213fe709a
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:292:9
  11:     0x607063811fc8 - std::panicking::rust_panic_with_hook::h095fccf1dc9379ee
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:779:13
  12:     0x607063811ea2 - std::panicking::begin_panic_handler::{{closure}}::h032ba12139b353db
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:657:13
  13:     0x607063810b86 - std::sys_common::backtrace::__rust_end_short_backtrace::h9259bc2ff8fd0f76
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:171:18
  14:     0x607063811c00 - rust_begin_unwind
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:645:5
  15:     0x6070633c6805 - core::panicking::panic_fmt::h784f20a50eaab275
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:72:14
  16:     0x6070633c6e43 - core::result::unwrap_failed::h03d8a5018196e1cd
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/result.rs:1649:5
  17:     0x6070633d6638 - core::result::Result<T,E>::expect::hee9e0eeebe8726d0
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/result.rs:1030:23
  18:     0x6070633d6638 - host::main::h0cfe23277e3581b0
                               at /home/user/src/zkpreimage/host/src/main.rs:26:19
  19:     0x6070633cf2b3 - core::ops::function::FnOnce::call_once::h1e862edc13c75a5f
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/ops/function.rs:250:5
  20:     0x6070633cf2b3 - std::sys_common::backtrace::__rust_begin_short_backtrace::hf3ca1823e35a112e
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:155:18
  21:     0x6070633c9be9 - std::rt::lang_start::{{closure}}::h5edd8c0ef85debd4
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/rt.rs:166:18
  22:     0x607063806ad1 - core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::h37600b1e5eea4ecd
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/ops/function.rs:284:13
  23:     0x607063806ad1 - std::panicking::try::do_call::hb4bda49fa13a0c2b
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:552:40
  24:     0x607063806ad1 - std::panicking::try::h8bbf75149211aaaa
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:516:19
  25:     0x607063806ad1 - std::panic::catch_unwind::h8c78ec68ebea34cb
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panic.rs:142:14
  26:     0x607063806ad1 - std::rt::lang_start_internal::{{closure}}::hffdf44a19fd9e220
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/rt.rs:148:48
  27:     0x607063806ad1 - std::panicking::try::do_call::hcb3194972c74716d
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:552:40
  28:     0x607063806ad1 - std::panicking::try::hcdc6892c5f0dba4c
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:516:19
  29:     0x607063806ad1 - std::panic::catch_unwind::h4910beb4573f4776
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panic.rs:142:14
  30:     0x607063806ad1 - std::rt::lang_start_internal::h6939038e2873596b
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/rt.rs:148:20
  31:     0x6070633d686c - main
  32:     0x77d02a678d0a - __libc_start_main
  33:     0x6070633c714a - _start
  34:                0x0 - <unknown>

Running in dev mode:

$ RISC0_DEV_MODE=true cargo run .
proving execution...
WARNING: proving in dev mode. This will not generate valid, secure proofs.
WARNING: Proving in dev mode does not generate a valid receipt. Receipts generated from this process are invalid and should never be used in production.
receipt journal output:
  hash:  72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793
  point: 031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f
verifying receipt...
OK

Running with only scalar finite field arithmetic instead of EC point multiplication:

diff --git a/methods/guest/src/main.rs b/methods/guest/src/main.rs
index 78e7b81..d104bb4 100644
--- a/methods/guest/src/main.rs
+++ b/methods/guest/src/main.rs
@@ -1,7 +1,7 @@
 #![no_main]
 #![no_std]
 
-use k256::elliptic_curve::group::GroupEncoding as _;
+// use k256::elliptic_curve::group::GroupEncoding as _;
 use risc0_zkvm::guest::env;
 use risc0_zkvm::sha;
 use sha::Sha256 as _;
@@ -11,8 +11,11 @@ risc0_zkvm::guest::entry!(main);
 #[allow(dead_code)]
 fn secp256k1_ec_mul(secret: &[u8; 32]) -> [u8; 33] {
     let seckey = k256::SecretKey::from_bytes(secret.into()).expect("secret key is invalid");
-    let pubkey = seckey.public_key();
-    pubkey.as_affine().to_bytes().into()
+    let scalar = seckey.to_nonzero_scalar() * seckey.to_nonzero_scalar();
+    let bytes = k256::FieldBytes::from(scalar);
+    let mut out = [0u8; 33];
+    out[1..].copy_from_slice(&bytes);
+    out
 }
 
 fn main() {
$ cargo run .
proving execution...
receipt journal output:
  hash:  72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793
  point: 0076710908241588c29833b5cfdfd42440cfcce085d0b9a247124793a078423c47
verifying receipt...
OK

So in summary:

  • Dev mode works fine.
  • Scalar multiplication in k256 works fine.
  • It's just EC point multiplication in prod mode which seems to have issues.

conduition avatar Feb 18 '24 16:02 conduition

I also tried using your fork of k256:

diff --git a/methods/guest/Cargo.toml b/methods/guest/Cargo.toml
index b74f270..71cd0d9 100644
--- a/methods/guest/Cargo.toml
+++ b/methods/guest/Cargo.toml
@@ -8,4 +8,18 @@ edition = "2021"
 [dependencies]
 # If you want to try (experimental) std support, add `features = [ "std" ]` to risc0-zkvm
 risc0-zkvm = { version = "0.20.1", default-features = false, features = [] }
-k256 = { version = "0.13.3", default-features = false, features = ["arithmetic", "serde"] }
+# k256 = { version = "0.13.3", default-features = false, features = ["arithmetic", "serde"] }
+
+[dependencies.k256]
+git = "https://github.com/risc0/RustCrypto-elliptic-curves"
+tag = "k256/v0.13.3-risczero.0"
+default-features = false
+features = ["arithmetic", "serde"]
+
+[patch.crates-io.crypto-bigint]
+git = "https://github.com/risc0/RustCrypto-crypto-bigint"
+tag = "v0.5.5-risczero.0"
+
+[patch.crates-io.sha2]
+git = "https://github.com/risc0/RustCrypto-hashes"
+tag = "sha2-v0.10.8-risczero.0"
$ cargo run .
proving execution...
thread 'main' panicked at host/src/main.rs:28:10:
failed to prove: failed to fill whole buffer
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

conduition avatar Feb 18 '24 16:02 conduition

Bump

conduition avatar Mar 25 '24 15:03 conduition

Bump

conduition avatar May 13 '24 15:05 conduition

@nategraf Any ideas?

flaub avatar May 13 '24 16:05 flaub

I'm not immediately sure. Using k256 without the patch, this should just be like any other standard Rust crate. It's definitely something I've definitely something I've done before (e.g. in our ecdsa example). The error itself is in the prover client that talks to the server subprocess. I've seen this before when the prover subprocess fails. I imagine it can also happen when there is a breaking protocol change and the client and the server are running different versions.

nategraf avatar May 13 '24 16:05 nategraf

I'll have to try running this code to see if I can reproduce it.

nategraf avatar May 13 '24 16:05 nategraf

I just retried with [email protected] and the crash still occurs. OP revised with code and config files for v1.0.1

conduition avatar Jul 08 '24 02:07 conduition

The crash seems to occur with any secp256k1 point multiplication operation regardless of which implementation i use. I have also tried using the secp256k1 crate (binds to libsecp256k1) in the guest zkVM, and I still get the exact same crash as if I had used k256 to perform the point multiplication.

Is there some kind of syscall or operation which these secp256k1 implementations are performing which is somehow causing a crash?

conduition avatar Jul 08 '24 03:07 conduition

I believe I found a much simpler reproduction method.

Guest program:

#![no_main]
#![no_std]

use num_bigint::{BigInt, Sign};
use num_integer::Integer;
use risc0_zkvm::guest::env;
use risc0_zkvm::sha;
use sha::Sha256 as _;

risc0_zkvm::guest::entry!(main);

fn modpow(base: &BigInt, exp: &BigInt, modulus: &BigInt) -> BigInt {
    let mut product = BigInt::from(1);
    let mut r = base.clone();
    for i in 0..10 {
        if exp.bit(i) {
            product = (&product * &r).mod_floor(modulus); // correct
        }
        r = (&r * &r).mod_floor(modulus); // correct
    }
    product
}

fn main() {
    let input: [u8; 32] = env::read();
    let mut output = [0u8; 32 + 33];

    // Compute SHA256 hash, and write it to the journal as a public output
    let digest = sha::Impl::hash_bytes(&input);
    output[..32].copy_from_slice(digest.as_bytes());

    let input_num = BigInt::from_bytes_be(Sign::Plus, input.as_ref());
    let output_num = {
        let modulus = BigInt::parse_bytes(
            b"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",
            16,
        )
        .unwrap();

        modpow(&input_num, &(&modulus - BigInt::from(2)), &modulus)
    };
    output[33..].copy_from_slice(output_num.to_bytes_be().1.as_ref());

    let output_bytes: &[u8] = output.as_ref();
    env::commit(&output_bytes);
}

The above program works fine. But with this one small modification, it fails.

--- a/methods/guest/src/main.rs
+++ b/methods/guest/src/main.rs
@@ -105,7 +105,7 @@ risc0_zkvm::guest::entry!(main);
 fn modpow(base: &BigInt, exp: &BigInt, modulus: &BigInt) -> BigInt {
     let mut product = BigInt::from(1);
     let mut r = base.clone();
-    for i in 0..10 {
+    for i in 0..100 {
         if exp.bit(i) {
             product = (&product * &r).mod_floor(modulus); // correct
         }

conduition avatar Jul 08 '24 18:07 conduition