ab_system_contract_simple_wallet_base/
seal.rs

1//! Utilities for [`Seal`] creation and verification
2
3use crate::{SIGNING_CONTEXT, Seal};
4use ab_contracts_common::ContractError;
5use ab_core_primitives::transaction::{TransactionHeader, TransactionSlot};
6use ab_io_type::trivial_type::TrivialType;
7use blake3::{Hasher, OUT_LEN};
8use core::slice;
9use schnorrkel::context::SigningContext;
10use schnorrkel::{Keypair, PublicKey, Signature};
11
12/// Create transaction hash used for signing with [`sign()`].
13///
14/// [`hash_and_sign()`] helper function exists that combines this method with [`sign()`].
15#[cfg_attr(feature = "no-panic", no_panic::no_panic)]
16pub fn hash_transaction(
17    header: &TransactionHeader,
18    read_slots: &[TransactionSlot],
19    write_slots: &[TransactionSlot],
20    payload: &[u128],
21    nonce: u64,
22) -> [u8; OUT_LEN] {
23    let mut hasher = Hasher::new();
24    hasher.update(header.as_bytes());
25    for slot in read_slots {
26        hasher.update(slot.as_bytes());
27    }
28    for slot in write_slots {
29        hasher.update(slot.as_bytes());
30    }
31    // SAFETY: Valid memory of correct size
32    let payload_bytes =
33        unsafe { slice::from_raw_parts(payload.as_ptr().cast::<u8>(), size_of_val(payload)) };
34    hasher.update(payload_bytes);
35    hasher.update(&nonce.to_le_bytes());
36    hasher.finalize().into()
37}
38
39/// Sign transaction hash created with [`hash_transaction()`].
40///
41/// [`hash_and_sign()`] helper function exists that combines this method with
42/// [`hash_transaction()`].
43#[cfg_attr(feature = "no-panic", no_panic::no_panic)]
44pub fn sign(keypair: &Keypair, tx_hash: &[u8; OUT_LEN]) -> Signature {
45    let signing_context = SigningContext::new(SIGNING_CONTEXT);
46    keypair.sign(signing_context.bytes(tx_hash))
47}
48
49/// Combines [`hash_transaction()`] and [`sign()`] and returns [`Seal`]
50#[cfg_attr(feature = "no-panic", no_panic::no_panic)]
51pub fn hash_and_sign(
52    keypair: &Keypair,
53    header: &TransactionHeader,
54    read_slots: &[TransactionSlot],
55    write_slots: &[TransactionSlot],
56    payload: &[u128],
57    nonce: u64,
58) -> Seal {
59    let tx_hash = hash_transaction(header, read_slots, write_slots, payload, nonce);
60    let signature = sign(keypair, &tx_hash).to_bytes();
61
62    Seal { signature, nonce }
63}
64
65/// Verify seal created by [`hash_and_sign()`].
66///
67/// [`hash_and_verify()`] helper function exists that combines this method with
68/// [`hash_transaction()`].
69#[cfg_attr(feature = "no-panic", no_panic::no_panic)]
70pub fn verify(
71    public_key: &PublicKey,
72    expected_nonce: u64,
73    tx_hash: &[u8; OUT_LEN],
74    signature: &Signature,
75    nonce: u64,
76) -> Result<(), ContractError> {
77    if nonce != expected_nonce {
78        return Err(ContractError::BadInput);
79    }
80
81    let signing_context = SigningContext::new(SIGNING_CONTEXT);
82    public_key
83        .verify(signing_context.bytes(tx_hash.as_bytes()), signature)
84        .map_err(|_error| ContractError::Forbidden)
85}
86
87/// Combines [`hash_transaction()`] and [`verify()`]
88#[cfg_attr(feature = "no-panic", no_panic::no_panic)]
89pub fn hash_and_verify(
90    public_key: &PublicKey,
91    expected_nonce: u64,
92    header: &TransactionHeader,
93    read_slots: &[TransactionSlot],
94    write_slots: &[TransactionSlot],
95    payload: &[u128],
96    seal: &Seal,
97) -> Result<(), ContractError> {
98    if seal.nonce != expected_nonce {
99        return Err(ContractError::BadInput);
100    }
101
102    let tx_hash = hash_transaction(header, read_slots, write_slots, payload, seal.nonce);
103    let signature =
104        Signature::from_bytes(&seal.signature).map_err(|_error| ContractError::BadInput)?;
105    let signing_context = SigningContext::new(SIGNING_CONTEXT);
106    public_key
107        .verify(signing_context.bytes(tx_hash.as_bytes()), &signature)
108        .map_err(|_error| ContractError::Forbidden)
109}