Skip to main content

arcium_client/
utils.rs

1use crate::idl::BLSDomainSeparator;
2use solana_alt_bn128_bls::{BLSError, G1Point, PrivKey as BLSPrivKey, Sha256Normalized};
3use solana_program::{hash::hashv, pubkey::Pubkey};
4
5#[cfg(feature = "transactions")]
6pub type X25519PubKey = x25519_dalek::PublicKey;
7#[cfg(feature = "transactions")]
8pub type X25519PrivKey = x25519_dalek::StaticSecret;
9
10// Maximum realloc size per ix
11pub const MAX_REALLOC_SIZE_PER_IX: usize = 10240; // 10KB = 10 * 1024
12
13// Maximum size of a Solana account in bytes.
14pub const MAX_SOL_ACCOUNT_SIZE_BYTES: usize = 10485760; // 10MB = 10 * 1024 * 1024
15
16// Size of the metadata in the raw circuit account. 1 byte for the bump, 8 for the discriminator.
17pub const METADATA_SIZE_RAW_CIRCUIT_ACC: usize = 9;
18
19// Maximum number of recovery peers per MXE.
20pub const MAX_RECOVERY_PEERS: usize = 100;
21
22// Minimal number of recovery peers per MXE.
23pub const MIN_RECOVERY_PEERS: usize = 4;
24
25// Maximum number of circuit bytes per raw circuit account.
26pub const MAX_RAW_CIRCUIT_BYTES_PER_ACC: usize =
27    MAX_SOL_ACCOUNT_SIZE_BYTES - METADATA_SIZE_RAW_CIRCUIT_ACC;
28
29pub(crate) fn sha256(vals: &[&[u8]]) -> [u8; 32] {
30    hashv(vals).to_bytes()
31}
32
33/// Generates a BLS Proof of Possession (PoP) signature.
34///
35/// The PoP proves ownership of the BLS secret key by signing a message derived from
36/// the BLS public key and the operator's Solana pubkey. This prevents rogue key attacks.
37pub fn generate_bls_pop_signature(
38    bls_privkey: &BLSPrivKey,
39    bls_pubkey_bytes: &[u8; 64],
40    operator_pubkey: &Pubkey,
41) -> Result<[u8; 64], BLSError> {
42    let pop_message = [
43        &[BLSDomainSeparator::PoP as u8][..],
44        operator_pubkey.as_ref(),
45        bls_pubkey_bytes.as_ref(),
46    ]
47    .concat();
48
49    let signature: G1Point = bls_privkey.sign::<Sha256Normalized, _>(&pop_message)?;
50    Ok(signature.0)
51}
52
53pub fn parse_bls_keypair(keypair_str: &str) -> Result<BLSPrivKey, BLSError> {
54    let key_array =
55        parse_byte_array::<32>(keypair_str).map_err(|_| BLSError::SerializationError)?;
56    Ok(BLSPrivKey(key_array))
57}
58
59/// Parse a byte array from a string.
60/// The string should be in the format
61/// `"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]"`.
62/// Returns an error if the string is not in the correct format.
63pub fn parse_byte_array<const N: usize>(bytes_str: &str) -> Result<[u8; N], String> {
64    let bytes_str = bytes_str.trim();
65    if !bytes_str.starts_with('[') || !bytes_str.ends_with(']') {
66        return Err("Invalid byte array format".to_string());
67    }
68    let inner = &bytes_str[1..bytes_str.len() - 1];
69    let bytes: Vec<u8> = inner
70        .split(',')
71        .map(|s| s.trim().parse::<u8>())
72        .collect::<Result<Vec<u8>, _>>()
73        .map_err(|_| "Failed to parse byte array".to_string())?;
74    let byte_array: [u8; N] = bytes
75        .try_into()
76        .map_err(|_| "Failed to parse byte array".to_string())?;
77    Ok(byte_array)
78}
79
80pub fn serialize_bls_keypair(keypair: &BLSPrivKey) -> String {
81    format!("{:?}", keypair.0)
82}
83
84#[cfg(feature = "transactions")]
85pub fn serialize_x25519_keypair(keypair: &X25519PrivKey) -> Result<String, serde_json::Error> {
86    serde_json::to_string(&keypair.as_bytes())
87}
88
89#[cfg(feature = "transactions")]
90pub fn parse_x25519_keypair(keypair_str: &str) -> Result<X25519PrivKey, serde_json::Error> {
91    serde_json::from_str(keypair_str)
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_bls_keypair_round_trip() {
100        // Create a keypair with known bytes
101        let original = BLSPrivKey([
102            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
103            25, 26, 27, 28, 29, 30, 31, 32,
104        ]);
105
106        // Serialize it
107        let serialized = serialize_bls_keypair(&original);
108
109        // Parse it back
110        let parsed = parse_bls_keypair(&serialized).expect("Failed to parse serialized keypair");
111
112        // Should match original
113        assert_eq!(original.0, parsed.0);
114    }
115
116    #[test]
117    fn test_bls_keypair_serialize_format() {
118        let keypair = BLSPrivKey([
119            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
120            25, 26, 27, 28, 29, 30, 31, 32,
121        ]);
122        let serialized = serialize_bls_keypair(&keypair);
123
124        // Should be in array format
125        assert!(serialized.starts_with('['));
126        assert!(serialized.ends_with(']'));
127        assert!(serialized.contains(", "));
128    }
129
130    #[test]
131    fn test_bls_keypair_parse_valid() {
132        let input = "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]";
133        let parsed = parse_bls_keypair(input).expect("Failed to parse valid keypair");
134
135        assert_eq!(parsed.0[0], 1);
136        assert_eq!(parsed.0[31], 32);
137    }
138
139    #[test]
140    fn test_bls_keypair_parse_with_whitespace() {
141        let input = "  [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]  ";
142        let parsed = parse_bls_keypair(input).expect("Failed to parse keypair with whitespace");
143
144        assert_eq!(parsed.0[0], 1);
145        assert_eq!(parsed.0[31], 32);
146    }
147
148    #[test]
149    fn test_bls_keypair_parse_invalid_format() {
150        let inputs = vec!["not an array", "[1, 2, 3", "1, 2, 3]", "{1, 2, 3}", ""];
151
152        for input in inputs {
153            assert!(
154                parse_bls_keypair(input).is_err(),
155                "Should fail for input: {}",
156                input
157            );
158        }
159    }
160
161    #[test]
162    fn test_bls_keypair_parse_wrong_length() {
163        // Too few bytes
164        let input = "[1, 2, 3]";
165        assert!(parse_bls_keypair(input).is_err());
166
167        // Too many bytes
168        let input = "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]";
169        assert!(parse_bls_keypair(input).is_err());
170    }
171
172    #[test]
173    fn test_bls_keypair_parse_invalid_numbers() {
174        // Not a number
175        let input = "[1, 2, abc, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]";
176        assert!(parse_bls_keypair(input).is_err());
177
178        // Out of u8 range
179        let input = "[256, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]";
180        assert!(parse_bls_keypair(input).is_err());
181    }
182}