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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use js_export_macro::js_export;
use miden_client::transaction::ChainAnchor as NativeChainAnchor;
use super::block_header::BlockHeader;
use super::word::Word;
use crate::platform::{JsBytes, JsErr};
use crate::utils::{deserialize_untrusted_bytes, serialize_to_bytes};
/// A self-contained, verifiable anchor that pins transaction execution to a specific reference
/// block instead of the client's current sync height.
///
/// Since protocol 0.16 a signed transaction summary binds the reference block commitment, so
/// signatures collected over a summary only authorize an execution whose reference block is the
/// one the summary was built at. Flows that collect signatures and execute later — multisig
/// proposals, offline co-signing — capture an anchor alongside the summary and replay execution
/// against it, so the summary reproduces exactly on a client at a different sync height,
/// provided both parties agree on the account state.
///
/// The anchor bundles the reference block header with a partial blockchain consistent with it.
/// Both invariants (chain length matches the header's block number, peaks hash to the header's
/// chain commitment) are enforced natively on construction and on [`Self::deserialize`], so an
/// anchor received from an untrusted party can never be malformed — only pinned to the wrong
/// block, or to one that never existed. To rule out the wrong block, compare [`Self::commitment`]
/// against `TransactionSummary::blockCommitment()`, which is signed into the summary. Re-deriving
/// the summary at the anchor and comparing `toCommitment()` is the stronger check, since it also
/// binds the request and the local account state.
///
/// Neither detects a fabricated block: both of the invariants above are computable over an
/// entirely invented chain. Fetch the header for [`Self::block_num`] from a node and compare
/// commitments to confirm the block is real. A transaction on a nonexistent block cannot be
/// submitted and its signature cannot be moved onto a real one, so the cost is a wasted proof
/// rather than loss of funds — but the header supplies the block number, timestamp and fee
/// parameters that execution runs against.
#[derive(Clone)]
#[js_export]
pub struct ChainAnchor(NativeChainAnchor);
#[js_export]
impl ChainAnchor {
/// Serializes the anchor into bytes, for shipping alongside a summary awaiting signatures.
pub fn serialize(&self) -> JsBytes {
serialize_to_bytes(&self.0)
}
/// Deserializes an anchor from bytes.
///
/// Rejects bytes whose partial blockchain is inconsistent with the header, so an anchor from
/// an untrusted source cannot be malformed — only pinned to the wrong block, which
/// [`Self::commitment`] detects. Trailing bytes are rejected, since these bytes arrive from a
/// counterparty rather than local storage and the blob is naturally treated as an identity.
///
/// The encoding carries no version tag, so anchors are only interchangeable between parties
/// on compatible SDK versions; a skew surfaces here as a generic deserialization failure.
pub fn deserialize(bytes: JsBytes) -> Result<ChainAnchor, JsErr> {
deserialize_untrusted_bytes::<NativeChainAnchor>(&bytes).map(ChainAnchor)
}
/// Returns the number of the anchored reference block.
#[js_export(js_name = "blockNum")]
pub fn block_num(&self) -> u32 {
self.0.block_num().as_u32()
}
/// Returns the commitment of the anchored reference block.
///
/// Compare this against an independently trusted commitment before executing with an anchor
/// from an untrusted source.
pub fn commitment(&self) -> Word {
self.0.block_commitment().into()
}
/// Returns the anchored reference block header.
#[js_export(js_name = "blockHeader")]
pub fn block_header(&self) -> BlockHeader {
self.0.header().into()
}
}
// CONVERSIONS
// ================================================================================================
impl From<ChainAnchor> for NativeChainAnchor {
fn from(anchor: ChainAnchor) -> Self {
anchor.0
}
}
impl From<&ChainAnchor> for NativeChainAnchor {
fn from(anchor: &ChainAnchor) -> Self {
anchor.0.clone()
}
}
impl From<NativeChainAnchor> for ChainAnchor {
fn from(anchor: NativeChainAnchor) -> Self {
ChainAnchor(anchor)
}
}
impl From<&NativeChainAnchor> for ChainAnchor {
fn from(anchor: &NativeChainAnchor) -> Self {
ChainAnchor(anchor.clone())
}
}
impl_napi_from_value!(ChainAnchor);