Skip to main content

world_id_primitives/
signer.rs

1use crate::PrimitiveError;
2use alloy::{primitives::Address, signers::local::PrivateKeySigner};
3use eddsa_babyjubjub::{EdDSAPrivateKey, EdDSAPublicKey};
4use secrecy::{ExposeSecret, SecretBox};
5
6/// The inner signer which can sign requests for both on-chain and off-chain operations. Both issuers and authenticators use this.
7///
8/// Both keys are zeroized on drop.
9#[derive(Debug)]
10pub struct Signer {
11    /// An on-chain `SECP256K1` private key. This key is used to sign operations that are validated on-chain (see `WorldIDRegistry` or `CredentialSchemaIssuerRegistry`).
12    onchain_signer: PrivateKeySigner,
13    /// An off-chain `EdDSA` private key. This key is used to sign operations that are validated off-chain, primarily within Zero-Knowledge Circuits.
14    offchain_signer: SecretBox<EdDSAPrivateKey>,
15}
16
17impl Signer {
18    /// Initializes a new signer from an input seed.
19    ///
20    /// # Errors
21    /// Returns `PrimitiveError::InvalidInput` if the seed is not exactly 32 bytes.
22    pub fn from_seed_bytes(seed: &[u8]) -> Result<Self, PrimitiveError> {
23        if seed.len() != 32 {
24            return Err(PrimitiveError::InvalidInput {
25                attribute: "seed".to_string(),
26                reason: format!("must be 32 bytes, got {} bytes", seed.len()),
27            });
28        }
29        let bytes: [u8; 32] = seed.try_into().map_err(|_| PrimitiveError::InvalidInput {
30            attribute: "seed".to_string(),
31            reason: "failed to convert to [u8; 32]".to_string(),
32        })?;
33        let onchain_signer = PrivateKeySigner::from_bytes(&bytes.into()).map_err(|e| {
34            PrimitiveError::InvalidInput {
35                attribute: "seed".to_string(),
36                reason: format!("invalid private key: {e}"),
37            }
38        })?;
39        let offchain_signer = SecretBox::new(Box::new(EdDSAPrivateKey::from_bytes(bytes)));
40
41        Ok(Self {
42            onchain_signer,
43            offchain_signer,
44        })
45    }
46
47    /// Returns a mutable reference to the internal signer.
48    #[expect(
49        clippy::missing_const_for_fn,
50        reason = "cannot be initialized at compile time"
51    )]
52    pub fn onchain_signer(&self) -> &PrivateKeySigner {
53        &self.onchain_signer
54    }
55
56    /// Returns a reference to the internal offchain signer.
57    pub const fn offchain_signer_private_key(&self) -> &SecretBox<EdDSAPrivateKey> {
58        &self.offchain_signer
59    }
60
61    /// Returns the address of the on-chain signer.
62    pub const fn onchain_signer_address(&self) -> Address {
63        self.onchain_signer.address()
64    }
65
66    /// Returns the public key of the off-chain signer.
67    pub fn offchain_signer_pubkey(&self) -> EdDSAPublicKey {
68        self.offchain_signer.expose_secret().public()
69    }
70}