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::signing::sign_verified;
7use crate::types::{PositionType, TakerSide};
8use crate::wire::{WireError, decode_base58_32, decode_base58_64, encode_base58, encode_hex};
9use crate::{AsyncSignerLike, SigningError};
10
11pub const ORDER_ID_LEN: usize = 32;
12pub const ORDER_DOMAIN_TAG: [u8; 4] = *b"ACTA";
13
14const TAG_LEN: usize = 4;
15const CHAIN_ID_LEN: usize = 8;
16const PUBKEY_LEN: usize = 32;
17const BOOL_LEN: usize = 1;
18const U64_LEN: usize = 8;
19const HEADER_LEN: usize = TAG_LEN + CHAIN_ID_LEN + PUBKEY_LEN;
20
21const OFF_IS_TAKER_BUY: usize = 0;
22const OFF_POSITION_TYPE: usize = OFF_IS_TAKER_BUY + BOOL_LEN;
23const OFF_MARKET: usize = OFF_POSITION_TYPE + BOOL_LEN;
24const OFF_STRIKE: usize = OFF_MARKET + PUBKEY_LEN;
25const OFF_QUANTITY: usize = OFF_STRIKE + U64_LEN;
26const OFF_GROSS_PRICE: usize = OFF_QUANTITY + U64_LEN;
27const OFF_VALID_UNTIL: usize = OFF_GROSS_PRICE + U64_LEN;
28const OFF_MAKER: usize = OFF_VALID_UNTIL + U64_LEN;
29const OFF_TAKER: usize = OFF_MAKER + PUBKEY_LEN;
30const OFF_NONCE: usize = OFF_TAKER + PUBKEY_LEN;
31const _: () = assert!(HEADER_LEN + OFF_NONCE + U64_LEN == ORDER_PREIMAGE_LEN);
32
33/// Layout: tag(4) + chain_id(8) + program_id(32) + is_taker_buy(1) +
34///         position_type(1) + market(32) + strike(8) + quantity(8) +
35///         gross_price(8) + valid_until(8) + maker(32) + taker(32) + nonce(8)
36pub const ORDER_PREIMAGE_LEN: usize = TAG_LEN
37    + CHAIN_ID_LEN
38    + PUBKEY_LEN
39    + BOOL_LEN
40    + BOOL_LEN
41    + PUBKEY_LEN
42    + U64_LEN
43    + U64_LEN
44    + U64_LEN
45    + U64_LEN
46    + PUBKEY_LEN
47    + PUBKEY_LEN
48    + U64_LEN;
49
50/// Synchronous order signer.
51pub trait SignerLike {
52    fn pubkey_bytes(&self) -> [u8; 32];
53    fn sign_message(&self, msg: &[u8]) -> [u8; 64];
54
55    fn pubkey_base58(&self) -> String {
56        encode_base58(&self.pubkey_bytes())
57    }
58
59    fn sign_message_base58(&self, msg: &[u8]) -> String {
60        encode_base58(&self.sign_message(msg))
61    }
62}
63
64/// In-memory Ed25519 signer. Secret material is zeroized on drop.
65#[derive(Clone)]
66pub struct BytesSigner {
67    signing_key: SigningKey,
68    pubkey: [u8; 32],
69}
70
71impl Zeroize for BytesSigner {
72    fn zeroize(&mut self) {
73        let _ = std::mem::replace(&mut self.signing_key, SigningKey::from_bytes(&[0u8; 32]));
74    }
75}
76
77impl ZeroizeOnDrop for BytesSigner {}
78
79impl BytesSigner {
80    pub fn from_secret(mut secret: [u8; 32]) -> Self {
81        let signing_key = SigningKey::from_bytes(&secret);
82        let pubkey = signing_key.verifying_key().to_bytes();
83        secret.zeroize();
84        Self {
85            signing_key,
86            pubkey,
87        }
88    }
89
90    pub fn from_keypair(keypair: &[u8; 64]) -> Result<Self, OrderError> {
91        let mut secret = [0u8; 32];
92        secret.copy_from_slice(&keypair[0..32]);
93        let signer = Self::from_secret(secret);
94        secret.zeroize();
95
96        if signer.pubkey != keypair[32..64] {
97            return Err(OrderError::KeypairPublicKeyMismatch);
98        }
99
100        Ok(signer)
101    }
102}
103
104impl SignerLike for BytesSigner {
105    fn pubkey_bytes(&self) -> [u8; 32] {
106        self.pubkey
107    }
108
109    fn sign_message(&self, msg: &[u8]) -> [u8; 64] {
110        let signature: Signature = self.signing_key.sign(msg);
111        signature.to_bytes()
112    }
113}
114
115#[derive(Debug, Clone)]
116pub struct OrderPreimageArgs {
117    pub chain_id: u64,
118    pub program_id: [u8; 32],
119    pub side: TakerSide,
120    pub position_type: PositionType,
121    pub market: [u8; 32],
122    pub strike: u64,
123    pub quantity: u64,
124    pub gross_price: u64,
125    pub valid_until: u64,
126    pub maker: [u8; 32],
127    pub taker: [u8; 32],
128    pub nonce: u64,
129}
130
131#[derive(Debug, Error)]
132pub enum OrderError {
133    #[error("spread types require the spread order layout")]
134    SpreadLayoutRequired,
135    #[error("keypair public key does not match its secret key")]
136    KeypairPublicKeyMismatch,
137    #[error("invalid signing key: {0}")]
138    InvalidSigningKey(String),
139    #[error("invalid verifying key: {0}")]
140    InvalidVerifyingKey(String),
141    #[error("signature verification failed")]
142    InvalidSignature,
143    #[error(transparent)]
144    Wire(#[from] WireError),
145}
146
147pub fn build_order_preimage(
148    args: &OrderPreimageArgs,
149) -> Result<[u8; ORDER_PREIMAGE_LEN], OrderError> {
150    if args.position_type.is_spread() {
151        return Err(OrderError::SpreadLayoutRequired);
152    }
153    Ok(build_physical_order_preimage(
154        args,
155        args.position_type == PositionType::CashSecuredPut,
156    ))
157}
158
159pub(crate) fn build_physical_order_preimage(
160    args: &OrderPreimageArgs,
161    is_put: bool,
162) -> [u8; ORDER_PREIMAGE_LEN] {
163    let mut buf = [0u8; ORDER_PREIMAGE_LEN];
164    buf[0..TAG_LEN].copy_from_slice(&ORDER_DOMAIN_TAG);
165    buf[TAG_LEN..TAG_LEN + CHAIN_ID_LEN].copy_from_slice(&args.chain_id.to_le_bytes());
166    buf[TAG_LEN + CHAIN_ID_LEN..HEADER_LEN].copy_from_slice(&args.program_id);
167
168    let b = HEADER_LEN;
169    buf[b + OFF_IS_TAKER_BUY] = u8::from(args.side.is_taker_buy());
170    buf[b + OFF_POSITION_TYPE] = u8::from(is_put);
171    buf[b + OFF_MARKET..b + OFF_MARKET + PUBKEY_LEN].copy_from_slice(&args.market);
172    buf[b + OFF_STRIKE..b + OFF_STRIKE + U64_LEN].copy_from_slice(&args.strike.to_le_bytes());
173    buf[b + OFF_QUANTITY..b + OFF_QUANTITY + U64_LEN].copy_from_slice(&args.quantity.to_le_bytes());
174    buf[b + OFF_GROSS_PRICE..b + OFF_GROSS_PRICE + U64_LEN]
175        .copy_from_slice(&args.gross_price.to_le_bytes());
176    buf[b + OFF_VALID_UNTIL..b + OFF_VALID_UNTIL + U64_LEN]
177        .copy_from_slice(&args.valid_until.to_le_bytes());
178    buf[b + OFF_MAKER..b + OFF_MAKER + PUBKEY_LEN].copy_from_slice(&args.maker);
179    buf[b + OFF_TAKER..b + OFF_TAKER + PUBKEY_LEN].copy_from_slice(&args.taker);
180    buf[b + OFF_NONCE..b + OFF_NONCE + U64_LEN].copy_from_slice(&args.nonce.to_le_bytes());
181    buf
182}
183
184pub fn hash_order_preimage(preimage: &[u8; ORDER_PREIMAGE_LEN]) -> [u8; ORDER_ID_LEN] {
185    let digest = Sha256::digest(preimage);
186    digest.into()
187}
188
189pub fn compute_order_id(args: &OrderPreimageArgs) -> Result<[u8; ORDER_ID_LEN], OrderError> {
190    Ok(hash_order_preimage(&build_order_preimage(args)?))
191}
192
193pub fn order_id_hex(order_id: &[u8; ORDER_ID_LEN]) -> String {
194    encode_hex(order_id)
195}
196
197pub fn order_preimage_hex(preimage: &[u8; ORDER_PREIMAGE_LEN]) -> String {
198    encode_hex(preimage)
199}
200
201/// Sign an order id from raw secret bytes.
202///
203/// Repeated signing should reuse a [`BytesSigner`] with [`sign_order_id_with_signer`]
204/// to avoid expanding the key on each call.
205pub fn sign_order_id_bytes(
206    order_id: &[u8; ORDER_ID_LEN],
207    signing_key_bytes: &[u8; 32],
208) -> Result<[u8; 64], OrderError> {
209    let signing_key = SigningKey::from_bytes(signing_key_bytes);
210    let signature: Signature = signing_key.sign(order_id);
211    Ok(signature.to_bytes())
212}
213
214/// Base58 form of [`sign_order_id_bytes`], and it re-expands the key the same way.
215pub fn sign_order_id_base58(
216    order_id: &[u8; ORDER_ID_LEN],
217    signing_key_bytes: &[u8; 32],
218) -> Result<String, OrderError> {
219    let sig = sign_order_id_bytes(order_id, signing_key_bytes)?;
220    Ok(encode_base58(&sig))
221}
222
223pub fn sign_order_id_with_signer<S: SignerLike + ?Sized>(
224    order_id: &[u8; ORDER_ID_LEN],
225    signer: &S,
226) -> [u8; 64] {
227    signer.sign_message(order_id)
228}
229
230pub fn sign_order_id_with_signer_base58<S: SignerLike>(
231    order_id: &[u8; ORDER_ID_LEN],
232    signer: &S,
233) -> String {
234    signer.sign_message_base58(order_id)
235}
236
237pub async fn sign_order_id_with_async_signer<S: AsyncSignerLike + ?Sized>(
238    order_id: &[u8; ORDER_ID_LEN],
239    signer: &S,
240) -> Result<[u8; 64], SigningError> {
241    sign_verified(signer, order_id).await
242}
243
244pub fn verify_order_id_signature_bytes(
245    order_id: &[u8; ORDER_ID_LEN],
246    signature: &[u8; 64],
247    verifying_key_bytes: &[u8; 32],
248) -> Result<(), OrderError> {
249    let verifying_key = VerifyingKey::from_bytes(verifying_key_bytes)
250        .map_err(|err| OrderError::InvalidVerifyingKey(err.to_string()))?;
251    let signature = Signature::from_bytes(signature);
252    verifying_key
253        .verify(order_id, &signature)
254        .map_err(|_| OrderError::InvalidSignature)
255}
256
257pub fn verify_order_id_signature_base58(
258    order_id_hex: &str,
259    signature_base58: &str,
260    pubkey_base58: &str,
261) -> Result<(), OrderError> {
262    let order_id = decode_hex32(order_id_hex)?;
263    let signature = decode_base58_64(signature_base58)?;
264    let pubkey = decode_base58_32(pubkey_base58)?;
265    verify_order_id_signature_bytes(&order_id, &signature, &pubkey)
266}
267
268pub fn sign_order_id_from_base58_keypair(
269    order_id_hex: &str,
270    keypair_base58: &str,
271) -> Result<String, OrderError> {
272    let order_id = decode_hex32(order_id_hex)?;
273    let keypair = decode_base58_64(keypair_base58)?;
274    let signer = BytesSigner::from_keypair(&keypair)?;
275    Ok(sign_order_id_with_signer_base58(&order_id, &signer))
276}
277
278fn decode_hex32(hex_str: &str) -> Result<[u8; 32], OrderError> {
279    crate::wire::decode_hex_32(hex_str).map_err(OrderError::Wire)
280}
281
282mod spreads;
283pub use spreads::*;