1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//! The digest the prop margin pool's expiry cosigner signs.
//!
//! Mirrors `expiry_cosign_digest` in `contracts/prop-margin-pool/src/main.sw`
//! byte for byte. The two change together or every settlement stops
//! verifying; the ABI doc on `expire_or_rollover` says why the figures are
//! signed at all.
use fuels::{
crypto::Message,
types::ContractId,
};
use sha2::{
Digest,
Sha256,
};
/// Domain tag. Distinct from prop-account's `0x02`, so a signature issued for
/// one contract's cosigner cannot be replayed at the other even when the same
/// key sits behind both.
const EXPIRY_COSIGN_TAG: u8 = 0x03;
/// `sha256(tag ++ chain_id ++ pool ++ account ++ session_id ++ expires_at ++
/// outside ++ roll)`, integers big-endian, ids as their raw 32 bytes and the
/// decision as a single `0`/`1` byte.
pub fn generate_pool_expiry_digest(
chain_id: u64,
pool: ContractId,
account: ContractId,
session_id: u64,
expires_at: u64,
outside: u64,
roll: bool,
) -> [u8; 32] {
let mut bytes = Vec::with_capacity(1 + 8 + 32 + 32 + 8 + 8 + 8 + 1);
bytes.push(EXPIRY_COSIGN_TAG);
bytes.extend_from_slice(&chain_id.to_be_bytes());
bytes.extend_from_slice(pool.as_slice());
bytes.extend_from_slice(account.as_slice());
bytes.extend_from_slice(&session_id.to_be_bytes());
bytes.extend_from_slice(&expires_at.to_be_bytes());
bytes.extend_from_slice(&outside.to_be_bytes());
bytes.push(u8::from(roll));
Sha256::digest(&bytes).into()
}
/// The same digest as a signable message: the pool recovers with
/// `Message::from(digest)`, so the payload is the digest itself.
pub fn generate_pool_expiry_signing_payload(
chain_id: u64,
pool: ContractId,
account: ContractId,
session_id: u64,
expires_at: u64,
outside: u64,
roll: bool,
) -> Message {
Message::from_bytes(generate_pool_expiry_digest(
chain_id, pool, account, session_id, expires_at, outside, roll,
))
}