Skip to content

Commit 3d82c1d

Browse files
committed
wip
1 parent f9d1433 commit 3d82c1d

File tree

6 files changed

+208
-232
lines changed

6 files changed

+208
-232
lines changed

crates/cartridge/src/lib.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ pub mod vrf;
55

66
pub use client::Client;
77
pub use vrf::{
8-
bootstrap_vrf, derive_vrf_accounts, resolve_executable, vrf_secret_key_from_account_key,
9-
wait_for_http_ok, InfoResponse, RequestContext, SignedOutsideExecution, VrfBootstrap,
10-
VrfBootstrapConfig, VrfBootstrapResult, VrfClient, VrfClientError, VrfDerivedAccounts,
11-
VrfOutsideExecution, VrfService, VrfServiceConfig, VrfServiceProcess, VrfSidecarError,
12-
VrfSidecarResult, BOOTSTRAP_TIMEOUT, SIDECAR_TIMEOUT, VRF_ACCOUNT_SALT, VRF_CONSUMER_SALT,
13-
VRF_SERVER_PORT,
8+
bootstrap_vrf, get_vrf_account, resolve_executable, wait_for_http_ok, InfoResponse,
9+
RequestContext, SignedOutsideExecution, VrfBootstrap, VrfBootstrapConfig, VrfBootstrapResult,
10+
VrfClient, VrfClientError, VrfDerivedAccounts, VrfOutsideExecution, VrfService,
11+
VrfServiceConfig, VrfServiceProcess, VRF_ACCOUNT_SALT, VRF_CONSUMER_SALT,
12+
VRF_HARDCODED_SECRET_KEY, VRF_SERVER_PORT,
1413
};
1514

1615
#[rustfmt::skip]

crates/cartridge/src/vrf/bootstrap.rs

Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! VRF bootstrap and account derivation.
22
//!
33
//! This module handles:
4-
//! - Deriving VRF accounts and keys from source accounts
4+
//! - Deriving VRF accounts and keys from a fixed VRF secret
55
//! - Deploying VRF account and consumer contracts via RPC
66
//! - Setting up VRF public keys on deployed accounts
77
//!
@@ -34,6 +34,8 @@ use url::Url;
3434
pub const VRF_ACCOUNT_SALT: u64 = 0x54321;
3535
/// Salt used for deploying VRF consumer contracts via UDC.
3636
pub const VRF_CONSUMER_SALT: u64 = 0x67890;
37+
/// Hardcoded VRF secret key used to derive VRF account credentials.
38+
pub const VRF_HARDCODED_SECRET_KEY: u64 = 0x111;
3739
/// Default timeout for bootstrap operations.
3840
pub const BOOTSTRAP_TIMEOUT: Duration = Duration::from_secs(10);
3941

@@ -63,7 +65,7 @@ pub struct VrfBootstrapConfig {
6365
/// Result of VRF bootstrap operations.
6466
#[derive(Debug, Clone)]
6567
pub struct VrfBootstrapResult {
66-
/// The VRF secret key (derived from source account).
68+
/// The VRF secret key used by the VRF sidecar.
6769
pub secret_key: u64,
6870
pub vrf_account_address: ContractAddress,
6971
pub vrf_account_private_key: Felt,
@@ -74,8 +76,7 @@ pub struct VrfBootstrapResult {
7476
/// Derived VRF account information.
7577
#[derive(Debug, Clone)]
7678
pub struct VrfDerivedAccounts {
77-
pub source_address: ContractAddress,
78-
pub source_private_key: Felt,
79+
pub vrf_account_private_key: Felt,
7980
pub vrf_account_address: ContractAddress,
8081
pub vrf_public_key_x: Felt,
8182
pub vrf_public_key_y: Felt,
@@ -86,22 +87,19 @@ pub struct VrfDerivedAccounts {
8687
// Bootstrap Functions
8788
// ============================================================================
8889

89-
/// Derive VRF accounts from source account.
90+
/// Derive VRF accounts from the hardcoded VRF secret key.
9091
///
9192
/// This computes the deterministic VRF account address and VRF key pair
92-
/// from the source account's private key.
93-
pub fn derive_vrf_accounts(
94-
bootstrapper_address: ContractAddress,
95-
bootstrapper_private_key: Felt,
96-
) -> Result<VrfDerivedAccounts> {
97-
// vrf-server expects a u64 secret, so derive one from the account key.
98-
let secret_key = vrf_secret_key_from_account_key(bootstrapper_private_key);
93+
/// from a fixed VRF private key.
94+
pub fn get_vrf_account() -> Result<VrfDerivedAccounts> {
95+
let secret_key = VRF_HARDCODED_SECRET_KEY;
96+
let vrf_account_private_key = Felt::from(secret_key);
9997
let public_key = generate_public_key(scalar_from_felt(secret_key.into()));
10098
let vrf_public_key_x = felt_from_field(public_key.x)?;
10199
let vrf_public_key_y = felt_from_field(public_key.y)?;
102100

103101
let account_public_key =
104-
SigningKey::from_secret_scalar(bootstrapper_private_key).verifying_key().scalar();
102+
SigningKey::from_secret_scalar(vrf_account_private_key).verifying_key().scalar();
105103

106104
let vrf_account_class_hash = CartridgeVrfAccount::HASH;
107105
// When using UDC with unique=0 (non-unique deployment), the deployer_address
@@ -115,8 +113,7 @@ pub fn derive_vrf_accounts(
115113
.into();
116114

117115
Ok(VrfDerivedAccounts {
118-
source_address: bootstrapper_address,
119-
source_private_key: bootstrapper_private_key,
116+
vrf_account_private_key,
120117
vrf_account_address,
121118
vrf_public_key_x,
122119
vrf_public_key_y,
@@ -135,11 +132,10 @@ pub async fn bootstrap_vrf(
135132
let chain_id_felt = provider.chain_id().await.context("failed to get chain ID from node")?;
136133
let chain_id = ChainId::Id(chain_id_felt);
137134

138-
let derived =
139-
derive_vrf_accounts(bootstrapper_account_address, bootstrapper_account_private_key)?;
135+
let derived = get_vrf_account()?;
140136
let vrf_account_address = derived.vrf_account_address;
141137
let account_public_key =
142-
SigningKey::from_secret_scalar(bootstrapper_account_private_key).verifying_key().scalar();
138+
SigningKey::from_secret_scalar(derived.vrf_account_private_key).verifying_key().scalar();
143139

144140
let vrf_account_class_hash = CartridgeVrfAccount::HASH;
145141

@@ -205,7 +201,7 @@ pub async fn bootstrap_vrf(
205201
// Set VRF public key on the deployed account
206202
// Create account for the VRF account to call set_vrf_public_key on itself
207203
let vrf_signer =
208-
LocalWallet::from(SigningKey::from_secret_scalar(bootstrapper_account_private_key));
204+
LocalWallet::from(SigningKey::from_secret_scalar(derived.vrf_account_private_key));
209205
let mut vrf_account = SingleOwnerAccount::new(
210206
provider.clone(),
211207
vrf_signer,
@@ -258,8 +254,7 @@ pub async fn bootstrap_vrf(
258254
vrf_consumer_address,
259255
chain_id,
260256
vrf_account_address,
261-
// right now, we take the bootstrapper account private key as the vrf account's private key
262-
vrf_account_private_key: bootstrapper_account_private_key,
257+
vrf_account_private_key: derived.vrf_account_private_key,
263258
})
264259
}
265260

@@ -350,37 +345,29 @@ fn scalar_from_felt(value: Felt) -> ScalarField {
350345
ScalarField::from_be_bytes_mod_order(&bytes)
351346
}
352347

353-
/// Derive a u64 VRF secret key from an account private key.
354-
///
355-
/// Uses the low 64 bits of the account key.
356-
pub fn vrf_secret_key_from_account_key(value: Felt) -> u64 {
357-
let bytes = value.to_bytes_be();
358-
let mut tail = [0_u8; 8];
359-
tail.copy_from_slice(&bytes[24..]);
360-
u64::from_be_bytes(tail)
361-
}
362-
363348
fn felt_from_field<T: std::fmt::Display>(value: T) -> Result<Felt> {
364349
let decimal = value.to_string();
365350
Felt::from_dec_str(&decimal).map_err(|err| anyhow!("invalid field value: {err}"))
366351
}
367352

368353
#[cfg(test)]
369354
mod tests {
370-
use katana_primitives::Felt;
371-
372-
use super::vrf_secret_key_from_account_key;
355+
use super::{get_vrf_account, VRF_HARDCODED_SECRET_KEY};
373356

374357
#[test]
375-
fn vrf_secret_key_uses_low_64_bits() {
376-
let mut bytes = [0_u8; 32];
377-
for (i, byte) in bytes.iter_mut().enumerate() {
378-
*byte = i as u8;
379-
}
358+
fn derive_vrf_accounts_uses_hardcoded_secret_key() {
359+
let derived = get_vrf_account().expect("must derive");
360+
assert_eq!(derived.secret_key, VRF_HARDCODED_SECRET_KEY);
361+
assert_eq!(derived.vrf_account_private_key, VRF_HARDCODED_SECRET_KEY.into());
362+
}
380363

381-
let felt = Felt::from_bytes_be(&bytes);
382-
let secret = vrf_secret_key_from_account_key(felt);
364+
#[test]
365+
fn derive_vrf_accounts_is_deterministic() {
366+
let first = get_vrf_account().expect("first derivation");
367+
let second = get_vrf_account().expect("second derivation");
383368

384-
assert_eq!(secret, 0x18191a1b1c1d1e1f);
369+
assert_eq!(first.vrf_account_address, second.vrf_account_address);
370+
assert_eq!(first.vrf_public_key_x, second.vrf_public_key_x);
371+
assert_eq!(first.vrf_public_key_y, second.vrf_public_key_y);
385372
}
386373
}

crates/cartridge/src/vrf/mod.rs

Lines changed: 168 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,177 @@
55
//! - Bootstrap logic for deploying VRF contracts
66
//! - Sidecar process management
77
8+
mod bootstrap;
89
mod client;
910

10-
pub mod bootstrap;
11-
pub mod sidecar;
11+
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
12+
use std::path::{Path, PathBuf};
13+
use std::process::Stdio;
14+
use std::time::{Duration, Instant};
15+
use std::{env, io};
1216

1317
pub use bootstrap::{
14-
bootstrap_vrf, derive_vrf_accounts, vrf_secret_key_from_account_key, VrfBootstrap,
15-
VrfBootstrapConfig, VrfBootstrapResult, VrfDerivedAccounts, BOOTSTRAP_TIMEOUT,
16-
VRF_ACCOUNT_SALT, VRF_CONSUMER_SALT,
18+
bootstrap_vrf, get_vrf_account, VrfBootstrap, VrfBootstrapConfig, VrfBootstrapResult,
19+
VrfDerivedAccounts, BOOTSTRAP_TIMEOUT, VRF_ACCOUNT_SALT, VRF_CONSUMER_SALT,
20+
VRF_HARDCODED_SECRET_KEY,
1721
};
1822
pub use client::*;
19-
pub use sidecar::{
20-
resolve_executable, wait_for_http_ok, Error as VrfSidecarError, Result as VrfSidecarResult,
21-
VrfService, VrfServiceConfig, VrfServiceProcess, SIDECAR_TIMEOUT, VRF_SERVER_PORT,
22-
};
23+
use katana_primitives::{ContractAddress, Felt};
24+
use tokio::process::{Child, Command};
25+
use tokio::time::sleep;
26+
use tracing::{debug, info, warn};
27+
use url::Url;
28+
29+
const LOG_TARGET: &str = "katana::cartridge::vrf::sidecar";
30+
31+
pub const VRF_SERVER_PORT: u16 = 3000;
32+
const DEFAULT_VRF_SERVICE_PATH: &str = "vrf-server";
33+
pub const SIDECAR_TIMEOUT: Duration = Duration::from_secs(10);
34+
35+
#[derive(Debug, thiserror::Error)]
36+
pub enum Error {
37+
#[error("bootstrap_result not set - call bootstrap() or bootstrap_result()")]
38+
BootstrapResultNotSet,
39+
#[error("sidecar binary not found at {0}")]
40+
BinaryNotFound(PathBuf),
41+
#[error("sidecar binary '{0}' not found in PATH")]
42+
BinaryNotInPath(PathBuf),
43+
#[error("PATH environment variable is not set")]
44+
PathNotSet,
45+
#[error("failed to spawn VRF sidecar")]
46+
Spawn(#[source] io::Error),
47+
#[error("VRF sidecar did not become ready before timeout")]
48+
SidecarTimeout,
49+
#[error("bootstrap failed")]
50+
Bootstrap(#[source] anyhow::Error),
51+
}
52+
53+
pub type Result<T, E = Error> = std::result::Result<T, E>;
54+
55+
#[derive(Debug, Clone)]
56+
pub struct VrfServiceConfig {
57+
pub vrf_account_address: ContractAddress,
58+
pub vrf_private_key: Felt,
59+
pub secret_key: u64,
60+
}
61+
62+
#[derive(Debug, Clone)]
63+
pub struct VrfService {
64+
config: VrfServiceConfig,
65+
path: PathBuf,
66+
}
67+
68+
impl VrfService {
69+
pub fn new(config: VrfServiceConfig) -> Self {
70+
Self { config, path: PathBuf::from(DEFAULT_VRF_SERVICE_PATH) }
71+
}
72+
73+
/// Sets the path to the vrf service program.
74+
///
75+
/// If no path is set, the default executable name [`DEFAULT_VRF_SERVICE_PATH`] will be used.
76+
pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
77+
self.path = path.into();
78+
self
79+
}
80+
81+
pub async fn start(self) -> Result<VrfServiceProcess> {
82+
let bin = resolve_executable(&self.path)?;
83+
84+
let mut command = Command::new(bin);
85+
command
86+
.arg("--port")
87+
.arg(VRF_SERVER_PORT.to_string())
88+
.arg("--account-address")
89+
.arg(self.config.vrf_account_address.to_hex_string())
90+
.arg("--account-private-key")
91+
.arg(self.config.vrf_private_key.to_hex_string())
92+
.arg("--secret-key")
93+
.arg(self.config.secret_key.to_string())
94+
.stdout(Stdio::inherit())
95+
.stderr(Stdio::inherit())
96+
.kill_on_drop(true);
97+
98+
let process = command.spawn().map_err(Error::Spawn)?;
99+
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), VRF_SERVER_PORT);
100+
101+
let url = Url::parse(&format!("http://{addr}")).expect("valid url");
102+
let client = VrfClient::new(url);
103+
wait_for_http_ok(&client, "vrf info", SIDECAR_TIMEOUT).await?;
104+
105+
info!(%addr, "VRF service started.");
106+
107+
Ok(VrfServiceProcess { process, addr, inner: self })
108+
}
109+
}
110+
111+
/// A running VRF sidecar process.
112+
#[derive(Debug)]
113+
pub struct VrfServiceProcess {
114+
process: Child,
115+
inner: VrfService,
116+
addr: SocketAddr,
117+
}
118+
119+
impl VrfServiceProcess {
120+
/// Get the address of the VRF service.
121+
pub fn addr(&self) -> &SocketAddr {
122+
&self.addr
123+
}
124+
125+
pub fn process(&mut self) -> &mut Child {
126+
&mut self.process
127+
}
128+
129+
pub fn config(&self) -> &VrfServiceConfig {
130+
&self.inner.config
131+
}
132+
133+
pub async fn shutdown(&mut self) -> io::Result<()> {
134+
self.process.kill().await
135+
}
136+
}
137+
138+
/// Resolve an executable path, searching in PATH if necessary.
139+
pub fn resolve_executable(path: &Path) -> Result<PathBuf> {
140+
if path.components().count() > 1 {
141+
return if path.is_file() {
142+
Ok(path.to_path_buf())
143+
} else {
144+
Err(Error::BinaryNotFound(path.to_path_buf()))
145+
};
146+
}
147+
148+
let path_var = env::var_os("PATH").ok_or(Error::PathNotSet)?;
149+
for dir in env::split_paths(&path_var) {
150+
let candidate = dir.join(path);
151+
if candidate.is_file() {
152+
return Ok(candidate);
153+
}
154+
}
155+
156+
Err(Error::BinaryNotInPath(path.to_path_buf()))
157+
}
158+
159+
/// Wait for the VRF sidecar to become ready by polling its `/info` endpoint.
160+
pub async fn wait_for_http_ok(client: &VrfClient, name: &str, timeout: Duration) -> Result<()> {
161+
let start = Instant::now();
162+
163+
loop {
164+
match client.info().await {
165+
Ok(_) => {
166+
info!(target: LOG_TARGET, %name, "sidecar ready");
167+
return Ok(());
168+
}
169+
Err(err) => {
170+
debug!(target: LOG_TARGET, %name, error = %err, "waiting for sidecar");
171+
}
172+
}
173+
174+
if start.elapsed() > timeout {
175+
warn!(target: LOG_TARGET, %name, "sidecar did not become ready in time");
176+
return Err(Error::SidecarTimeout);
177+
}
178+
179+
sleep(Duration::from_millis(200)).await;
180+
}
181+
}

0 commit comments

Comments
 (0)