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