Skip to content

Commit 13910d4

Browse files
committed
feat: Patch secp256k1 0.29.0
1 parent 6648126 commit 13910d4

File tree

5 files changed

+97
-1
lines changed

5 files changed

+97
-1
lines changed

Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ global-context-less-secure = ["global-context"]
3838
[dependencies]
3939
secp256k1-sys = { version = "0.10.0", default-features = false, path = "./secp256k1-sys" }
4040
serde = { version = "1.0.103", default-features = false, optional = true }
41+
cfg-if = "1.0"
4142

4243
# You likely only want to enable these if you explicitly do not want to use "std", otherwise enable
4344
# the respective -std feature e.g., hashes-std
@@ -54,6 +55,11 @@ bincode = "1.3.3"
5455
wasm-bindgen-test = "0.3"
5556
getrandom = { version = "0.2", features = ["js"] }
5657

58+
[target.'cfg(all(target_os = "zkvm", target_vendor = "succinct"))'.dependencies]
59+
sp1-ecdsa = { git = "https://github.com/sp1-patches/signatures", branch = "patch-ecdsa-v0.16.9", package = "ecdsa", features = ["verifying", "alloc"] }
60+
k256 = { version = "0.13.3", features = ["ecdsa"] }
61+
elliptic-curve = { version = "0.13.6", default-features = false, features = ["digest", "sec1"] }
62+
5763

5864
[[example]]
5965
name = "sign_verify_recovery"

secp256k1-sys/build.rs

+35
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,41 @@ use std::env;
1616
fn main() {
1717
// Actual build
1818
let mut base_config = cc::Build::new();
19+
20+
// On riscv32, we need to build C libraries using the riscv-gnu-toolchain and
21+
// clang for compiling C code.
22+
if env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "riscv32" {
23+
const DEFAULT_RISCV_GNU_TOOLCHAIN: &str = "/opt/riscv";
24+
println!("cargo:rerun-if-env-changed=RISCV_GNU_TOOLCHAIN");
25+
26+
let riscv_gnu_toolchain_path = env::var("RISCV_GNU_TOOLCHAIN").unwrap_or_else(|_| {
27+
println!("cargo:warning=Variable RISCV_GNU_TOOLCHAIN unset. Assuming '{DEFAULT_RISCV_GNU_TOOLCHAIN}'");
28+
println!("cargo:warning=Please make sure to build riscv toolchain:");
29+
println!("cargo:warning= git clone https://github.com/riscv-collab/riscv-gnu-toolchain && cd riscv-gnu-toolchain");
30+
println!("cargo:warning= export RISCV_GNU_TOOLCHAIN={DEFAULT_RISCV_GNU_TOOLCHAIN}");
31+
println!("cargo:warning= configure --prefix=\"$RISCV_GNU_TOOLCHAIN\" --with-arch=rv32im --with-abi=ilp32");
32+
println!("cargo:warning= make -j$(nproc)");
33+
34+
// if unset, try the default and fail eventually
35+
DEFAULT_RISCV_GNU_TOOLCHAIN.into()
36+
});
37+
38+
base_config
39+
.compiler("clang")
40+
.no_default_flags(true)
41+
.flag(&format!("--sysroot={riscv_gnu_toolchain_path}/riscv32-unknown-elf"))
42+
.flag(&format!("--gcc-toolchain={riscv_gnu_toolchain_path}"))
43+
.flag("--target=riscv32-unknown-none-elf")
44+
.flag("-march=rv32im")
45+
.flag("-mabi=ilp32")
46+
.flag("-mcmodel=medany")
47+
.flag("-Os")
48+
.flag("-fdata-sections")
49+
.flag("-ffunction-sections")
50+
.flag("-flto")
51+
.target("riscv32-unknown-none-elf");
52+
}
53+
1954
base_config.include("depend/secp256k1/")
2055
.include("depend/secp256k1/include")
2156
.include("depend/secp256k1/src")

src/ecdsa/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
//! Structs and functionality related to the ECDSA signature algorithm.
44
//!
5+
#[cfg(all(target_os = "zkvm", target_vendor = "succinct"))]
6+
use elliptic_curve;
7+
#[cfg(all(target_os = "zkvm", target_vendor = "succinct"))]
8+
use k256;
9+
#[cfg(all(target_os = "zkvm", target_vendor = "succinct"))]
10+
use sp1_ecdsa;
511

612
#[cfg(feature = "recovery")]
713
mod recovery;

src/ecdsa/recovery.rs

+49
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,20 @@ impl<C: Signing> Secp256k1<C> {
184184
}
185185
}
186186

187+
/// Takes a 64 byte array and reverses the endian-ness of each 32 byte segment.
188+
/// Useful for flipping the byte endian-ness of the signature and public key components.
189+
fn flip_secp256k1_endianness(input: &[u8; 64]) -> [u8; 64] {
190+
let mut output = [0u8; 64];
191+
for i in 0..2 {
192+
let start = i * 32;
193+
let end = start + 32;
194+
let mut segment: [u8; 32] = input[start..end].try_into().unwrap();
195+
segment.reverse();
196+
output[start..end].copy_from_slice(&segment);
197+
}
198+
output
199+
}
200+
187201
impl<C: Verification> Secp256k1<C> {
188202
/// Determines the public key for which `sig` is a valid signature for
189203
/// `msg`. Requires a verify-capable context.
@@ -192,6 +206,41 @@ impl<C: Verification> Secp256k1<C> {
192206
msg: &Message,
193207
sig: &RecoverableSignature,
194208
) -> Result<key::PublicKey, Error> {
209+
cfg_if::cfg_if! {
210+
if #[cfg(all(target_os = "zkvm", target_vendor = "succinct"))] {
211+
// `msg.0` contains the message data as a byte array.
212+
let prehash: &[u8] = &msg.0;
213+
214+
// `sig` is a 65-byte array containing r (32 bytes), s (32 bytes), and the recovery ID (1 byte). We need to handle the
215+
// signature bytes according to their endianness. The signature is in little-endian format, but needs to be processed
216+
// in big-endian format for masking by the ecdsa-core library.
217+
218+
// Reverse the first 32 bytes (r) and the second 32 bytes (s) of the signature
219+
// and concatenate them to get the signature in big-endian format.
220+
let mut sig_be_bytes = flip_secp256k1_endianness(&sig.0[..64].try_into().unwrap());
221+
222+
let signature = sp1_ecdsa::Signature::<k256::Secp256k1>::from_slice(&sig_be_bytes).unwrap();
223+
224+
// The recovery ID is the last byte of the signature.
225+
let recovery_id = sp1_ecdsa::RecoveryId::from_byte(sig.0[64]).unwrap();
226+
227+
let verifying_key = sp1_ecdsa::VerifyingKey::recover_from_prehash_secp256k1(prehash, &signature, recovery_id).unwrap();
228+
let verifying_key_bytes = {
229+
// Convert the verifying key to a byte array. The encoded point returned by `to_encoded_point` is in uncompressed format,
230+
// with the prefix byte (0x04) and two 32-byte coordinates in big-endian format. This needs to be flipped to little-endian
231+
// for the from_array_unchecked constructor.
232+
let bytes = verifying_key.to_encoded_point(false).to_bytes();
233+
flip_secp256k1_endianness(&bytes[1..65].try_into().unwrap())
234+
};
235+
236+
// In recover_from_prehash_secp256k1, the public key is verified to be valid, so we can use the unchecked constructor.
237+
unsafe {
238+
let k = key::PublicKey(crate::ffi::PublicKey::from_array_unchecked(verifying_key_bytes));
239+
return Ok(k);
240+
}
241+
}
242+
}
243+
195244
unsafe {
196245
let mut pk = super_ffi::PublicKey::new();
197246
if ffi::secp256k1_ecdsa_recover(

src/key.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl str::FromStr for SecretKey {
146146
/// [`cbor`]: https://docs.rs/cbor
147147
#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
148148
#[repr(transparent)]
149-
pub struct PublicKey(ffi::PublicKey);
149+
pub struct PublicKey(pub ffi::PublicKey);
150150
impl_fast_comparisons!(PublicKey);
151151

152152
impl fmt::LowerHex for PublicKey {

0 commit comments

Comments
 (0)