Skip to main content

acta_maker_sdk/orders/
mod.rs

1use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
2use sha2::{Digest, Sha256};
3use thiserror::Error;
4use zeroize::{Zeroize, ZeroizeOnDrop};
5
6use crate::wire::{WireError, decode_base58_32, decode_base58_64, encode_base58, encode_hex};
7
8pub const ORDER_ID_LEN: usize = 32;
9pub const ORDER_DOMAIN_TAG: [u8; 4] = *b"ACTA";
10
11const TAG_LEN: usize = 4;
12const CHAIN_ID_LEN: usize = 8;
13const PUBKEY_LEN: usize = 32;
14const BOOL_LEN: usize = 1;
15const U64_LEN: usize = 8;
16const HEADER_LEN: usize = TAG_LEN + CHAIN_ID_LEN + PUBKEY_LEN;
17
18// Body field offsets (relative to HEADER_LEN).
19const OFF_IS_TAKER_BUY: usize = 0;
20const OFF_POSITION_TYPE: usize = OFF_IS_TAKER_BUY + BOOL_LEN;
21const OFF_MARKET: usize = OFF_POSITION_TYPE + BOOL_LEN;
22const OFF_STRIKE: usize = OFF_MARKET + PUBKEY_LEN;
23const OFF_QUANTITY: usize = OFF_STRIKE + U64_LEN;
24const OFF_GROSS_PRICE: usize = OFF_QUANTITY + U64_LEN;
25const OFF_VALID_UNTIL: usize = OFF_GROSS_PRICE + U64_LEN;
26const OFF_MAKER: usize = OFF_VALID_UNTIL + U64_LEN;
27const OFF_TAKER: usize = OFF_MAKER + PUBKEY_LEN;
28const OFF_NONCE: usize = OFF_TAKER + PUBKEY_LEN;
29const _: () = assert!(HEADER_LEN + OFF_NONCE + U64_LEN == ORDER_PREIMAGE_LEN);
30
31/// Layout: tag(4) + chain_id(8) + program_id(32) + is_taker_buy(1) +
32///         position_type(1) + market(32) + strike(8) + quantity(8) +
33///         gross_price(8) + valid_until(8) + maker(32) + taker(32) + nonce(8)
34pub const ORDER_PREIMAGE_LEN: usize = TAG_LEN
35    + CHAIN_ID_LEN
36    + PUBKEY_LEN
37    + BOOL_LEN
38    + BOOL_LEN
39    + PUBKEY_LEN
40    + U64_LEN
41    + U64_LEN
42    + U64_LEN
43    + U64_LEN
44    + PUBKEY_LEN
45    + PUBKEY_LEN
46    + U64_LEN;
47
48/// Trait for abstracting signing operations.
49///
50/// Allows using different key implementations without
51/// depending on specific crates like `solana-sdk`.
52pub trait SignerLike {
53    fn pubkey_bytes(&self) -> [u8; 32];
54    fn sign_message(&self, msg: &[u8]) -> [u8; 64];
55
56    fn pubkey_base58(&self) -> String {
57        encode_base58(&self.pubkey_bytes())
58    }
59
60    fn sign_message_base58(&self, msg: &[u8]) -> String {
61        encode_base58(&self.sign_message(msg))
62    }
63}
64
65/// Simple signer implementation using a cached ed25519 `SigningKey`.
66///
67/// The `SigningKey` is created once and reused for all signing operations,
68/// avoiding per-call reconstruction overhead. `SigningKey` from `ed25519-dalek`
69/// implements `Zeroize` on drop, so secret material is securely cleared.
70#[derive(Clone)]
71pub struct BytesSigner {
72    signing_key: SigningKey,
73    pubkey: [u8; 32],
74}
75
76impl Zeroize for BytesSigner {
77    fn zeroize(&mut self) {
78        let _ = std::mem::replace(&mut self.signing_key, SigningKey::from_bytes(&[0u8; 32]));
79    }
80}
81
82impl ZeroizeOnDrop for BytesSigner {}
83
84impl BytesSigner {
85    pub fn from_secret(secret: [u8; 32]) -> Self {
86        let signing_key = SigningKey::from_bytes(&secret);
87        let pubkey = signing_key.verifying_key().to_bytes();
88        Self {
89            signing_key,
90            pubkey,
91        }
92    }
93
94    pub fn from_keypair(keypair: &[u8; 64]) -> Self {
95        let mut secret = [0u8; 32];
96        secret.copy_from_slice(&keypair[0..32]);
97        Self::from_secret(secret)
98    }
99}
100
101impl SignerLike for BytesSigner {
102    fn pubkey_bytes(&self) -> [u8; 32] {
103        self.pubkey
104    }
105
106    fn sign_message(&self, msg: &[u8]) -> [u8; 64] {
107        let signature: Signature = self.signing_key.sign(msg);
108        signature.to_bytes()
109    }
110}
111
112#[derive(Debug, Clone)]
113pub struct OrderPreimageArgs {
114    pub chain_id: u64,
115    pub program_id: [u8; 32],
116    pub is_taker_buy: bool,
117    pub position_type: u8,
118    pub market: [u8; 32],
119    pub strike: u64,
120    pub quantity: u64,
121    pub gross_price: u64,
122    pub valid_until: u64,
123    pub maker: [u8; 32],
124    pub taker: [u8; 32],
125    pub nonce: u64,
126}
127
128#[derive(Debug, Error)]
129pub enum OrderError {
130    #[error("invalid signing key: {0}")]
131    InvalidSigningKey(String),
132    #[error("invalid verifying key: {0}")]
133    InvalidVerifyingKey(String),
134    #[error("signature verification failed")]
135    InvalidSignature,
136    #[error(transparent)]
137    Wire(#[from] WireError),
138}
139
140pub fn build_order_preimage(args: &OrderPreimageArgs) -> [u8; ORDER_PREIMAGE_LEN] {
141    let mut buf = [0u8; ORDER_PREIMAGE_LEN];
142    buf[0..TAG_LEN].copy_from_slice(&ORDER_DOMAIN_TAG);
143    buf[TAG_LEN..TAG_LEN + CHAIN_ID_LEN].copy_from_slice(&args.chain_id.to_le_bytes());
144    buf[TAG_LEN + CHAIN_ID_LEN..HEADER_LEN].copy_from_slice(&args.program_id);
145
146    let b = HEADER_LEN;
147    buf[b + OFF_IS_TAKER_BUY] = u8::from(args.is_taker_buy);
148    buf[b + OFF_POSITION_TYPE] = args.position_type;
149    buf[b + OFF_MARKET..b + OFF_MARKET + PUBKEY_LEN].copy_from_slice(&args.market);
150    buf[b + OFF_STRIKE..b + OFF_STRIKE + U64_LEN].copy_from_slice(&args.strike.to_le_bytes());
151    buf[b + OFF_QUANTITY..b + OFF_QUANTITY + U64_LEN].copy_from_slice(&args.quantity.to_le_bytes());
152    buf[b + OFF_GROSS_PRICE..b + OFF_GROSS_PRICE + U64_LEN]
153        .copy_from_slice(&args.gross_price.to_le_bytes());
154    buf[b + OFF_VALID_UNTIL..b + OFF_VALID_UNTIL + U64_LEN]
155        .copy_from_slice(&args.valid_until.to_le_bytes());
156    buf[b + OFF_MAKER..b + OFF_MAKER + PUBKEY_LEN].copy_from_slice(&args.maker);
157    buf[b + OFF_TAKER..b + OFF_TAKER + PUBKEY_LEN].copy_from_slice(&args.taker);
158    buf[b + OFF_NONCE..b + OFF_NONCE + U64_LEN].copy_from_slice(&args.nonce.to_le_bytes());
159    buf
160}
161
162pub fn hash_order_preimage(preimage: &[u8; ORDER_PREIMAGE_LEN]) -> [u8; ORDER_ID_LEN] {
163    let digest = Sha256::digest(preimage);
164    digest.into()
165}
166
167pub fn compute_order_id(args: &OrderPreimageArgs) -> [u8; ORDER_ID_LEN] {
168    hash_order_preimage(&build_order_preimage(args))
169}
170
171pub fn order_id_hex(order_id: &[u8; ORDER_ID_LEN]) -> String {
172    encode_hex(order_id)
173}
174
175pub fn order_preimage_hex(preimage: &[u8; ORDER_PREIMAGE_LEN]) -> String {
176    encode_hex(preimage)
177}
178
179pub fn sign_order_id_bytes(
180    order_id: &[u8; ORDER_ID_LEN],
181    signing_key_bytes: &[u8; 32],
182) -> Result<[u8; 64], OrderError> {
183    let signing_key = SigningKey::from_bytes(signing_key_bytes);
184    let signature: Signature = signing_key.sign(order_id);
185    Ok(signature.to_bytes())
186}
187
188pub fn sign_order_id_base58(
189    order_id: &[u8; ORDER_ID_LEN],
190    signing_key_bytes: &[u8; 32],
191) -> Result<String, OrderError> {
192    let sig = sign_order_id_bytes(order_id, signing_key_bytes)?;
193    Ok(encode_base58(&sig))
194}
195
196pub fn sign_order_id_with_signer<S: SignerLike>(
197    order_id: &[u8; ORDER_ID_LEN],
198    signer: &S,
199) -> [u8; 64] {
200    signer.sign_message(order_id)
201}
202
203pub fn sign_order_id_with_signer_base58<S: SignerLike>(
204    order_id: &[u8; ORDER_ID_LEN],
205    signer: &S,
206) -> String {
207    signer.sign_message_base58(order_id)
208}
209
210pub fn verify_order_id_signature_bytes(
211    order_id: &[u8; ORDER_ID_LEN],
212    signature: &[u8; 64],
213    verifying_key_bytes: &[u8; 32],
214) -> Result<(), OrderError> {
215    let verifying_key = VerifyingKey::from_bytes(verifying_key_bytes)
216        .map_err(|err| OrderError::InvalidVerifyingKey(err.to_string()))?;
217    let signature = Signature::from_bytes(signature);
218    verifying_key
219        .verify(order_id, &signature)
220        .map_err(|_| OrderError::InvalidSignature)
221}
222
223pub fn verify_order_id_signature_base58(
224    order_id_hex: &str,
225    signature_base58: &str,
226    pubkey_base58: &str,
227) -> Result<(), OrderError> {
228    let order_id = decode_hex32(order_id_hex)?;
229    let signature = decode_base58_64(signature_base58)?;
230    let pubkey = decode_base58_32(pubkey_base58)?;
231    verify_order_id_signature_bytes(&order_id, &signature, &pubkey)
232}
233
234pub fn sign_order_id_from_base58_keypair(
235    order_id_hex: &str,
236    keypair_base58: &str,
237) -> Result<String, OrderError> {
238    let order_id = decode_hex32(order_id_hex)?;
239    let keypair = decode_base58_64(keypair_base58)?;
240    let secret: [u8; 32] = keypair[0..32]
241        .try_into()
242        .map_err(|_| OrderError::InvalidSigningKey("expected 64-byte keypair".to_string()))?;
243    sign_order_id_base58(&order_id, &secret)
244}
245
246fn decode_hex32(hex_str: &str) -> Result<[u8; 32], OrderError> {
247    crate::wire::decode_hex_32(hex_str).map_err(OrderError::Wire)
248}