forked from Dshield-xyz/Dshield
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreport.ts
More file actions
114 lines (106 loc) · 4.38 KB
/
Copy pathreport.ts
File metadata and controls
114 lines (106 loc) · 4.38 KB
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
107
108
109
110
111
112
113
114
import * as StellarSdk from "@stellar/stellar-sdk";
import { POOL_CONTRACT_ID, queryContract } from "./stellar";
import { computeCommitment, computeNullifierHash } from "./poseidon2";
import { fetchCommitmentsFromChain, lookupNoteTxs } from "./indexer";
import { type ShieldedNote } from "./notes";
import { getNetworkLabel } from "./explorer";
export interface ComplianceReport {
network: string;
poolId: string;
/** 0x-prefixed 32-byte commitment, recomputed from the note. */
commitment: string;
/** 0x-prefixed 32-byte nullifier hash, derived from the note. */
nullifierHash: string;
/** Whether the commitment recomputed from the note matches the note's stored commitment. */
integrityOk: boolean;
/** The commitment was found in the pool's on-chain commitment list. */
depositConfirmed: boolean;
/** Leaf index of the commitment on-chain, or null if not found. */
leafIndex: number | null;
/** The nullifier has been spent on-chain (funds withdrawn). */
withdrawn: boolean;
depositTx: { hash: string; at: string } | null;
withdrawTx: { hash: string; at: string } | null;
generatedAt: number;
}
/**
* Build a compliance report for a note from authoritative on-chain data.
* Deliberately omits amounts, addresses, AND the note itself (nullifier +
* secret): the note is a bearer-spendable credential, so embedding it in a
* report meant to be shared with a third party (an auditor, a PDF export)
* would hand them the ability to withdraw the funds. Reproducing this report
* requires the note out of band, from the holder directly.
*/
export async function buildComplianceReport(
note: ShieldedNote,
): Promise<ComplianceReport> {
const poolId = note.poolId || POOL_CONTRACT_ID;
if (!poolId) throw new Error("No pool configured for this note.");
// Re-derive the commitment and nullifier hash from the note's secrets.
const commitment = await computeCommitment(note.nullifier, note.secret);
const nullifierHash = await computeNullifierHash(note.nullifier);
const commitmentClean = commitment.replace(/^0x/, "").toLowerCase();
const integrityOk =
commitmentClean === note.commitment.replace(/^0x/, "").toLowerCase();
// Deposit confirmation: is the commitment in the pool's authoritative list?
const chainCommitments = await fetchCommitmentsFromChain(poolId);
let leafIndex: number | null = null;
if (chainCommitments) {
const idx = chainCommitments.findIndex(
(c) => c.replace(/^0x/, "").toLowerCase() === commitmentClean,
);
if (idx >= 0) leafIndex = idx;
}
const depositConfirmed = leafIndex !== null;
// Withdrawal status: has the nullifier been spent on-chain?
let withdrawn = false;
const usedVal = await queryContract(poolId, "is_nullifier_used", [
StellarSdk.xdr.ScVal.scvBytes(
Buffer.from(nullifierHash.replace(/^0x/, ""), "hex"),
),
]);
if (usedVal) withdrawn = StellarSdk.scValToNative(usedVal) === true;
// Best-effort: link the actual deposit/withdraw transactions.
const txs = await lookupNoteTxs(poolId, commitment, nullifierHash);
return {
network: getNetworkLabel(),
poolId,
commitment,
nullifierHash,
integrityOk,
depositConfirmed,
leafIndex,
withdrawn,
depositTx: txs.depositTx,
withdrawTx: txs.withdrawTx,
generatedAt: Date.now(),
};
}
/** Render a report as plain text for download / inspection. */
export function formatReportText(r: ComplianceReport): string {
const line = (k: string, v: string) => `${k.padEnd(20)}${v}`;
return [
"DShield Compliance Report",
"=========================",
line("Generated", new Date(r.generatedAt).toISOString()),
line("Network", r.network),
line("Pool contract", r.poolId),
"",
line("Note integrity", r.integrityOk ? "OK (commitment matches)" : "MISMATCH"),
line(
"Deposit",
r.depositConfirmed
? `Confirmed on-chain (leaf #${r.leafIndex})`
: "Not found on-chain",
),
line("Status", r.withdrawn ? "Withdrawn (nullifier spent)" : "In pool (unspent)"),
line("Commitment", r.commitment),
line("Nullifier hash", r.nullifierHash),
r.depositTx
? line("Deposit tx", `${r.depositTx.hash} (${r.depositTx.at})`)
: line("Deposit tx", "n/a (outside event retention)"),
r.withdrawTx
? line("Withdraw tx", `${r.withdrawTx.hash} (${r.withdrawTx.at})`)
: line("Withdraw tx", r.withdrawn ? "n/a (outside event retention)" : "—"),
].join("\n");
}