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