@supercuts-walrus/backend 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +249 -2
  3. package/dist/audit.d.ts +52 -0
  4. package/dist/audit.js +157 -0
  5. package/dist/backend.d.ts +27 -0
  6. package/dist/backend.js +62 -0
  7. package/dist/buyer-setup.d.ts +6 -0
  8. package/dist/buyer-setup.js +241 -0
  9. package/dist/capability.d.ts +32 -0
  10. package/dist/capability.js +87 -0
  11. package/dist/claude.d.ts +44 -0
  12. package/dist/claude.js +98 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +29 -0
  15. package/dist/config.d.ts +20 -0
  16. package/dist/config.js +58 -0
  17. package/dist/dashboard.d.ts +5 -0
  18. package/dist/dashboard.js +21 -0
  19. package/dist/deal-adapter.d.ts +3 -0
  20. package/dist/deal-adapter.js +39 -0
  21. package/dist/deal-engine.d.ts +62 -0
  22. package/dist/deal-engine.js +211 -0
  23. package/dist/deal-wire.d.ts +16 -0
  24. package/dist/deal-wire.js +65 -0
  25. package/dist/deal.d.ts +79 -0
  26. package/dist/deal.js +228 -0
  27. package/dist/demo.d.ts +2 -0
  28. package/dist/demo.js +449 -0
  29. package/dist/errors.d.ts +6 -0
  30. package/dist/errors.js +13 -0
  31. package/dist/events.d.ts +27 -0
  32. package/dist/events.js +68 -0
  33. package/dist/facilitator.d.ts +21 -0
  34. package/dist/facilitator.js +173 -0
  35. package/dist/finality.d.ts +2 -0
  36. package/dist/finality.js +27 -0
  37. package/dist/hosted-exit-cli.d.ts +2 -0
  38. package/dist/hosted-exit-cli.js +37 -0
  39. package/dist/hosted-exit.d.ts +24 -0
  40. package/dist/hosted-exit.js +70 -0
  41. package/dist/hosted-seller-import.d.ts +2 -0
  42. package/dist/hosted-seller-import.js +70 -0
  43. package/dist/hosted-seller-repository.d.ts +31 -0
  44. package/dist/hosted-seller-repository.js +214 -0
  45. package/dist/index.d.ts +37 -0
  46. package/dist/index.js +37 -0
  47. package/dist/indexer.d.ts +63 -0
  48. package/dist/indexer.js +60 -0
  49. package/dist/live-deal-ports.d.ts +41 -0
  50. package/dist/live-deal-ports.js +212 -0
  51. package/dist/live-index.d.ts +28 -0
  52. package/dist/live-index.js +149 -0
  53. package/dist/live-runtime.d.ts +82 -0
  54. package/dist/live-runtime.js +570 -0
  55. package/dist/live-seller-transport.d.ts +30 -0
  56. package/dist/live-seller-transport.js +169 -0
  57. package/dist/live-seller-verifier.d.ts +11 -0
  58. package/dist/live-seller-verifier.js +65 -0
  59. package/dist/mandate.d.ts +34 -0
  60. package/dist/mandate.js +178 -0
  61. package/dist/mcp-bootstrap.d.ts +2 -0
  62. package/dist/mcp-bootstrap.js +39 -0
  63. package/dist/mcp.d.ts +2 -0
  64. package/dist/mcp.js +187 -0
  65. package/dist/memory-worker.d.ts +2 -0
  66. package/dist/memory-worker.js +38 -0
  67. package/dist/memory.d.ts +15 -0
  68. package/dist/memory.js +17 -0
  69. package/dist/memwal.d.ts +35 -0
  70. package/dist/memwal.js +83 -0
  71. package/dist/negotiation.d.ts +23 -0
  72. package/dist/negotiation.js +58 -0
  73. package/dist/notes.d.ts +11 -0
  74. package/dist/notes.js +26 -0
  75. package/dist/onboarding-doctor.d.ts +1 -0
  76. package/dist/onboarding-doctor.js +76 -0
  77. package/dist/phase3.d.ts +100 -0
  78. package/dist/phase3.js +1144 -0
  79. package/dist/procurement.d.ts +8 -0
  80. package/dist/procurement.js +19 -0
  81. package/dist/protocol-engine.d.ts +121 -0
  82. package/dist/protocol-engine.js +149 -0
  83. package/dist/prover.d.ts +28 -0
  84. package/dist/prover.js +70 -0
  85. package/dist/relayer.d.ts +32 -0
  86. package/dist/relayer.js +43 -0
  87. package/dist/remote-buyer.d.ts +67 -0
  88. package/dist/remote-buyer.js +231 -0
  89. package/dist/resource-server.d.ts +2 -0
  90. package/dist/resource-server.js +24 -0
  91. package/dist/resource.d.ts +22 -0
  92. package/dist/resource.js +55 -0
  93. package/dist/seam.d.ts +2 -0
  94. package/dist/seam.js +117 -0
  95. package/dist/seller-catalog-generate.d.ts +2 -0
  96. package/dist/seller-catalog-generate.js +68 -0
  97. package/dist/seller-database.d.ts +41 -0
  98. package/dist/seller-database.js +80 -0
  99. package/dist/seller-directory-cli.d.ts +2 -0
  100. package/dist/seller-directory-cli.js +24 -0
  101. package/dist/seller-directory.d.ts +5 -0
  102. package/dist/seller-directory.js +50 -0
  103. package/dist/seller-live-cli.d.ts +2 -0
  104. package/dist/seller-live-cli.js +53 -0
  105. package/dist/seller-provision.d.ts +2 -0
  106. package/dist/seller-provision.js +84 -0
  107. package/dist/seller-register-cli.d.ts +2 -0
  108. package/dist/seller-register-cli.js +40 -0
  109. package/dist/seller-runtime.d.ts +60 -0
  110. package/dist/seller-runtime.js +96 -0
  111. package/dist/seller-service.d.ts +34 -0
  112. package/dist/seller-service.js +416 -0
  113. package/dist/seller-state.d.ts +19 -0
  114. package/dist/seller-state.js +60 -0
  115. package/dist/seller-stdio.d.ts +6 -0
  116. package/dist/seller-stdio.js +29 -0
  117. package/dist/seller-v2-provision.d.ts +2 -0
  118. package/dist/seller-v2-provision.js +71 -0
  119. package/dist/seller-watch.d.ts +2 -0
  120. package/dist/seller-watch.js +45 -0
  121. package/dist/settlement.d.ts +95 -0
  122. package/dist/settlement.js +141 -0
  123. package/dist/store.d.ts +36 -0
  124. package/dist/store.js +193 -0
  125. package/dist/sui.d.ts +49 -0
  126. package/dist/sui.js +77 -0
  127. package/dist/tatum.d.ts +17 -0
  128. package/dist/tatum.js +83 -0
  129. package/dist/transcript.d.ts +31 -0
  130. package/dist/transcript.js +52 -0
  131. package/dist/v2-exit-relayer.d.ts +34 -0
  132. package/dist/v2-exit-relayer.js +102 -0
  133. package/dist/v2-exit-worker.d.ts +2 -0
  134. package/dist/v2-exit-worker.js +97 -0
  135. package/dist/v2-exits.d.ts +30 -0
  136. package/dist/v2-exits.js +78 -0
  137. package/dist/v2-prefund.d.ts +19 -0
  138. package/dist/v2-prefund.js +43 -0
  139. package/dist/v2-relayer-cli.d.ts +2 -0
  140. package/dist/v2-relayer-cli.js +39 -0
  141. package/dist/wallet.d.ts +32 -0
  142. package/dist/wallet.js +90 -0
  143. package/dist/workflow-memory.d.ts +22 -0
  144. package/dist/workflow-memory.js +49 -0
  145. package/dist/x402-resource-service.d.ts +34 -0
  146. package/dist/x402-resource-service.js +154 -0
  147. package/dist/x402.d.ts +46 -0
  148. package/dist/x402.js +136 -0
  149. package/package.json +60 -3
@@ -0,0 +1,8 @@
1
+ export interface NegotiationBounds {
2
+ buyerOffer: bigint;
3
+ buyerMaximum: bigint;
4
+ sellerMinimum: bigint;
5
+ }
6
+ /** Amounts are backend-owned; the model can only choose whether to continue with this result. */
7
+ export declare function deterministicCounteroffer(bounds: NegotiationBounds): bigint;
8
+ export declare function settlementRailForPrivacy(privacy: "public" | "confidential", supported: ReadonlyArray<"x402" | "shielded">): "x402" | "shielded";
@@ -0,0 +1,19 @@
1
+ import { fail } from "./errors.js";
2
+ /** Amounts are backend-owned; the model can only choose whether to continue with this result. */
3
+ export function deterministicCounteroffer(bounds) {
4
+ const { buyerOffer, buyerMaximum, sellerMinimum } = bounds;
5
+ if (buyerOffer < 0n || buyerMaximum <= 0n || sellerMinimum <= 0n || buyerOffer > buyerMaximum)
6
+ fail("NEGOTIATION_BOUNDS_INVALID", "negotiation amounts are invalid");
7
+ if (sellerMinimum > buyerMaximum)
8
+ fail("NEGOTIATION_NO_OVERLAP", "seller minimum exceeds buyer maximum");
9
+ if (buyerOffer >= sellerMinimum)
10
+ return buyerOffer;
11
+ const midpoint = buyerOffer + (sellerMinimum - buyerOffer + 1n) / 2n;
12
+ return midpoint < sellerMinimum ? sellerMinimum : midpoint > buyerMaximum ? buyerMaximum : midpoint;
13
+ }
14
+ export function settlementRailForPrivacy(privacy, supported) {
15
+ const wanted = privacy === "confidential" ? "shielded" : "x402";
16
+ if (!supported.includes(wanted))
17
+ fail("RAIL_UNAVAILABLE", `${wanted} is required by deterministic privacy policy but is unavailable`);
18
+ return wanted;
19
+ }
@@ -0,0 +1,121 @@
1
+ export declare const TREE_DEPTH = 20;
2
+ export interface ProtocolDeployment {
3
+ deploymentDomain: string;
4
+ poolId: string;
5
+ coinTypeHash: string;
6
+ verifierVersion: number;
7
+ initialRoot: string;
8
+ }
9
+ export interface ProtocolIdentity {
10
+ seed: Uint8Array;
11
+ publicKey: [bigint, bigint];
12
+ }
13
+ export interface NoteOpeningData {
14
+ deployment: bigint;
15
+ amount: bigint;
16
+ ownerAx: bigint;
17
+ ownerAy: bigint;
18
+ spendSecret: bigint;
19
+ salt: bigint;
20
+ commitment: bigint;
21
+ leafIndex: bigint;
22
+ }
23
+ export interface AcceptedAgreement {
24
+ dealId: bigint;
25
+ revision: bigint;
26
+ transcriptRootLo: bigint;
27
+ transcriptRootHi: bigint;
28
+ serviceDigestLo: bigint;
29
+ serviceDigestHi: bigint;
30
+ settlementNonceLo: bigint;
31
+ settlementNonceHi: bigint;
32
+ paymentAmount: bigint;
33
+ blinding: bigint;
34
+ expiry: bigint;
35
+ buyer: ProtocolIdentity;
36
+ sellerPublicKey: [bigint, bigint];
37
+ sellerSignature: {
38
+ R8x: bigint;
39
+ R8y: bigint;
40
+ S: bigint;
41
+ };
42
+ recipientSpendTag: bigint;
43
+ }
44
+ export interface DepositWitness {
45
+ input: Record<string, unknown>;
46
+ opening: NoteOpeningData;
47
+ newRoot: bigint;
48
+ newIndex: bigint;
49
+ statement: Buffer;
50
+ }
51
+ export interface TransferWitness {
52
+ input: Record<string, unknown>;
53
+ statement: Buffer;
54
+ newRoot: bigint;
55
+ newIndex: bigint;
56
+ dealCommitment: bigint;
57
+ dealNullifier: bigint;
58
+ inputNullifier: bigint;
59
+ paymentCommitment: bigint;
60
+ changeCommitment: bigint;
61
+ paymentSalt: bigint;
62
+ changeOpening: NoteOpeningData;
63
+ }
64
+ export interface WithdrawWitness {
65
+ input: Record<string, unknown>;
66
+ statement: Buffer;
67
+ noteNullifier: bigint;
68
+ }
69
+ export interface WithdrawV2Witness extends WithdrawWitness {
70
+ newRoot: bigint;
71
+ newIndex: bigint;
72
+ changeOpening: NoteOpeningData;
73
+ exactExit: boolean;
74
+ amount: bigint;
75
+ }
76
+ export interface AgreementValues {
77
+ paymentSalt: bigint;
78
+ dealCommitment: bigint;
79
+ dealNullifier: bigint;
80
+ }
81
+ export declare class PoseidonTree {
82
+ readonly leaves: bigint[];
83
+ private readonly poseidon;
84
+ readonly zeroes: bigint[];
85
+ constructor(leaves: bigint[], poseidon: (values: bigint[]) => bigint);
86
+ path(selectedIndex: number): {
87
+ root: bigint;
88
+ siblings: bigint[];
89
+ };
90
+ get root(): bigint;
91
+ appendPath(): bigint[];
92
+ }
93
+ export declare class ProtocolEngine {
94
+ readonly deployment: ProtocolDeployment;
95
+ readonly deploymentField: bigint;
96
+ private readonly eddsa;
97
+ private readonly poseidonHash;
98
+ private constructor();
99
+ static create(deployment: ProtocolDeployment): Promise<ProtocolEngine>;
100
+ identity(seed?: Uint8Array): ProtocolIdentity;
101
+ randomScalar(): bigint;
102
+ spendTag(secret: bigint): bigint;
103
+ noteCommitment(amount: bigint, owner: [bigint, bigint], spendSecret: bigint, salt: bigint): bigint;
104
+ sign(identity: ProtocolIdentity, domain: 2004n | 2005n, agreement: bigint): {
105
+ R8x: bigint;
106
+ R8y: bigint;
107
+ S: bigint;
108
+ };
109
+ verify(publicKey: [bigint, bigint], domain: 2004n | 2005n, agreement: bigint, signature: {
110
+ R8x: bigint;
111
+ R8y: bigint;
112
+ S: bigint;
113
+ }): boolean;
114
+ private header;
115
+ tree(leaves: bigint[]): PoseidonTree;
116
+ buildDeposit(leaves: bigint[], owner: ProtocolIdentity, amount: bigint, spendSecret?: bigint, salt?: bigint): DepositWitness;
117
+ agreementValues(agreement: Omit<AcceptedAgreement, "sellerSignature">): AgreementValues;
118
+ buildTransfer(leaves: bigint[], inputNote: NoteOpeningData, agreement: AcceptedAgreement, changeSpendSecret?: bigint, changeSalt?: bigint): TransferWitness;
119
+ buildWithdraw(leaves: bigint[], note: NoteOpeningData, recipient: string): WithdrawWitness;
120
+ buildWithdrawV2(leaves: bigint[], note: NoteOpeningData, recipient: string, amount: bigint, exactExit?: boolean, changeSpendSecret?: bigint, changeSalt?: bigint): WithdrawV2Witness;
121
+ }
@@ -0,0 +1,149 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { buildEddsa } from "circomlibjs";
3
+ import { fail } from "./errors.js";
4
+ export const TREE_DEPTH = 20;
5
+ const FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
6
+ const asStrings = (value) => JSON.parse(JSON.stringify(value, (_key, item) => typeof item === "bigint" ? item.toString() : item));
7
+ const fieldBytes = (value) => Buffer.from(value.toString(16).padStart(64, "0"), "hex");
8
+ const u32 = (value) => { const out = Buffer.alloc(4); out.writeUInt32BE(value); return out; };
9
+ const u64 = (value) => { const out = Buffer.alloc(8); out.writeBigUInt64BE(value); return out; };
10
+ const limbs = (value) => [BigInt(`0x${value.subarray(0, 16).toString("hex")}`), BigInt(`0x${value.subarray(16).toString("hex")}`)];
11
+ const digestInputs = (statement) => { const digest = createHash("sha256").update(statement).digest(); return { digestHigh: BigInt(`0x${digest.subarray(0, 16).toString("hex")}`), digestLow: BigInt(`0x${digest.subarray(16).toString("hex")}`) }; };
12
+ const scalar = (bytes = 31) => { for (;;) {
13
+ const value = BigInt(`0x${randomBytes(bytes).toString("hex")}`);
14
+ if (value > 0n && value < FIELD_MODULUS)
15
+ return value;
16
+ } };
17
+ export class PoseidonTree {
18
+ leaves;
19
+ poseidon;
20
+ zeroes;
21
+ constructor(leaves, poseidon) {
22
+ this.leaves = leaves;
23
+ this.poseidon = poseidon;
24
+ if (leaves.length >= 2 ** TREE_DEPTH)
25
+ fail("TREE_EXHAUSTED", "protocol tree is full");
26
+ this.zeroes = [0n];
27
+ for (let level = 0; level < TREE_DEPTH; level++)
28
+ this.zeroes.push(poseidon([this.zeroes[level], this.zeroes[level]]));
29
+ }
30
+ path(selectedIndex) {
31
+ if (!Number.isSafeInteger(selectedIndex) || selectedIndex < 0 || selectedIndex >= 2 ** TREE_DEPTH)
32
+ fail("TREE_INDEX_INVALID", "tree index is outside the protocol range");
33
+ let nodes = new Map(this.leaves.map((leaf, index) => [index, leaf]));
34
+ let index = selectedIndex;
35
+ const siblings = [];
36
+ for (let level = 0; level < TREE_DEPTH; level++) {
37
+ siblings.push(nodes.get(index ^ 1) ?? this.zeroes[level]);
38
+ const parents = new Map();
39
+ for (const position of nodes.keys()) {
40
+ const parent = Math.floor(position / 2);
41
+ if (!parents.has(parent))
42
+ parents.set(parent, this.poseidon([nodes.get(parent * 2) ?? this.zeroes[level], nodes.get(parent * 2 + 1) ?? this.zeroes[level]]));
43
+ }
44
+ nodes = parents;
45
+ index = Math.floor(index / 2);
46
+ }
47
+ return { root: nodes.get(0) ?? this.zeroes[TREE_DEPTH], siblings };
48
+ }
49
+ get root() { return this.path(0).root; }
50
+ appendPath() { return this.path(this.leaves.length).siblings; }
51
+ }
52
+ export class ProtocolEngine {
53
+ deployment;
54
+ deploymentField;
55
+ eddsa;
56
+ poseidonHash;
57
+ constructor(deployment, deploymentField, eddsa, poseidonHash) {
58
+ this.deployment = deployment;
59
+ this.deploymentField = deploymentField;
60
+ this.eddsa = eddsa;
61
+ this.poseidonHash = poseidonHash;
62
+ }
63
+ static async create(deployment) {
64
+ const domain = Buffer.from(deployment.deploymentDomain.replace(/^0x/, ""), "hex"), pool = Buffer.from(deployment.poolId.replace(/^0x/, ""), "hex"), coin = Buffer.from(deployment.coinTypeHash.replace(/^0x/, ""), "hex");
65
+ if ([domain, pool, coin].some((value) => value.length !== 32) || !Number.isSafeInteger(deployment.verifierVersion) || deployment.verifierVersion < 1)
66
+ fail("CONFIG_INVALID", "protocol deployment fields are invalid");
67
+ const eddsa = await buildEddsa();
68
+ const F = eddsa.babyJub.F;
69
+ const poseidon = (values) => F.toObject(eddsa.poseidon(values));
70
+ const [domainHi, domainLo] = limbs(domain), [poolHi, poolLo] = limbs(pool), [coinHi, coinLo] = limbs(coin);
71
+ return new ProtocolEngine(deployment, poseidon([2100n, domainHi, domainLo, poolHi, poolLo, coinHi, coinLo, BigInt(deployment.verifierVersion)]), eddsa, poseidon);
72
+ }
73
+ identity(seed = randomBytes(32)) { if (seed.length !== 32)
74
+ fail("IDENTITY_INVALID", "BabyJub identity seed must contain 32 bytes"); const F = this.eddsa.babyJub.F; return { seed: Uint8Array.from(seed), publicKey: this.eddsa.prv2pub(seed).map((value) => F.toObject(value)) }; }
75
+ randomScalar() { return scalar(); }
76
+ spendTag(secret) { return this.poseidonHash([2006n, secret]); }
77
+ noteCommitment(amount, owner, spendSecret, salt) { return this.poseidonHash([2010n, this.deploymentField, amount, owner[0], owner[1], this.spendTag(spendSecret), salt]); }
78
+ sign(identity, domain, agreement) { const F = this.eddsa.babyJub.F; const signature = this.eddsa.signPoseidon(identity.seed, F.e(this.poseidonHash([domain, this.deploymentField, agreement]))); return { R8x: F.toObject(signature.R8[0]), R8y: F.toObject(signature.R8[1]), S: signature.S }; }
79
+ verify(publicKey, domain, agreement, signature) { const F = this.eddsa.babyJub.F; return this.eddsa.verifyPoseidon(F.e(this.poseidonHash([domain, this.deploymentField, agreement])), { R8: [F.e(signature.R8x), F.e(signature.R8y)], S: signature.S }, [F.e(publicKey[0]), F.e(publicKey[1])]); }
80
+ header(kind) { return Buffer.concat([Buffer.from("SCUT"), Buffer.from([this.deployment.verifierVersion === 2 ? 2 : 1, kind]), Buffer.from(this.deployment.deploymentDomain.replace(/^0x/, ""), "hex"), Buffer.from(this.deployment.poolId.replace(/^0x/, ""), "hex"), Buffer.from(this.deployment.coinTypeHash.replace(/^0x/, ""), "hex"), u32(this.deployment.verifierVersion)]); }
81
+ tree(leaves) { return new PoseidonTree(leaves, this.poseidonHash); }
82
+ buildDeposit(leaves, owner, amount, spendSecret = scalar(), salt = scalar()) {
83
+ if (amount <= 0n || amount >= 1n << 64n)
84
+ fail("AMOUNT_INVALID", "deposit amount must be a positive u64");
85
+ if (this.deployment.verifierVersion === 2 && amount !== 10000000n && amount !== 100000000n)
86
+ fail("AMOUNT_INVALID", "v2 deposits must be 10,000,000 or 100,000,000 MIST");
87
+ const tree = this.tree(leaves), oldRoot = tree.root, leafIndex = BigInt(leaves.length), commitment = this.noteCommitment(amount, owner.publicKey, spendSecret, salt), nextTree = this.tree([...leaves, commitment]), newRoot = nextTree.root, newIndex = leafIndex + 1n;
88
+ const expectedInitial = BigInt(`0x${this.deployment.initialRoot.replace(/^0x/, "")}`);
89
+ if (!leaves.length && oldRoot !== expectedInitial)
90
+ fail("INITIAL_ROOT_MISMATCH", "circuit zero tree differs from the pinned initial root");
91
+ const statement = Buffer.concat([this.header(1), u64(amount), fieldBytes(commitment), fieldBytes(oldRoot), u64(leafIndex), fieldBytes(newRoot), u64(newIndex)]);
92
+ const input = asStrings({ statement: [...statement], ...digestInputs(statement), ownerAx: owner.publicKey[0], ownerAy: owner.publicKey[1], spendSecret, salt, appendSiblings: tree.appendPath() });
93
+ return { input, opening: { deployment: this.deploymentField, amount, ownerAx: owner.publicKey[0], ownerAy: owner.publicKey[1], spendSecret, salt, commitment, leafIndex }, newRoot, newIndex, statement };
94
+ }
95
+ agreementValues(agreement) {
96
+ const buyer = agreement.buyer.publicKey, seller = agreement.sellerPublicKey, paymentSalt = this.poseidonHash([2009n, this.deploymentField, agreement.settlementNonceLo, agreement.settlementNonceHi]);
97
+ const termsA = this.poseidonHash([2001n, this.deploymentField, agreement.dealId, agreement.revision, agreement.transcriptRootLo, agreement.transcriptRootHi]);
98
+ const termsB = this.poseidonHash([agreement.serviceDigestLo, agreement.serviceDigestHi, agreement.settlementNonceLo, agreement.settlementNonceHi, agreement.paymentAmount, agreement.expiry]);
99
+ const termsC = this.poseidonHash([buyer[0], buyer[1], seller[0], seller[1], agreement.recipientSpendTag, paymentSalt]);
100
+ return { paymentSalt, dealCommitment: this.poseidonHash([2002n, termsA, termsB, termsC, agreement.blinding, agreement.expiry]), dealNullifier: this.poseidonHash([2003n, this.deploymentField, buyer[0], buyer[1], agreement.settlementNonceLo, agreement.settlementNonceHi]) };
101
+ }
102
+ buildTransfer(leaves, inputNote, agreement, changeSpendSecret = scalar(), changeSalt = scalar()) {
103
+ if (agreement.paymentAmount <= 0n || agreement.paymentAmount >= inputNote.amount || agreement.expiry <= BigInt(Date.now()))
104
+ fail("AGREEMENT_INVALID", "agreement amount must leave positive change and expiry must be in the future");
105
+ const buyer = agreement.buyer.publicKey, seller = agreement.sellerPublicKey, { paymentSalt, dealCommitment, dealNullifier } = this.agreementValues(agreement);
106
+ const buyerSignature = this.sign(agreement.buyer, 2004n, dealCommitment);
107
+ const inputCommitment = this.noteCommitment(inputNote.amount, buyer, inputNote.spendSecret, inputNote.salt);
108
+ if (inputCommitment !== inputNote.commitment || leaves[Number(inputNote.leafIndex)] !== inputCommitment)
109
+ fail("INPUT_NOTE_MISMATCH", "input note opening does not match indexed leaf");
110
+ const inputNullifier = this.poseidonHash([2011n, this.deploymentField, inputNote.spendSecret, inputCommitment]);
111
+ const paymentCommitment = this.poseidonHash([2010n, this.deploymentField, agreement.paymentAmount, seller[0], seller[1], agreement.recipientSpendTag, paymentSalt]);
112
+ const changeAmount = inputNote.amount - agreement.paymentAmount, changeCommitment = this.noteCommitment(changeAmount, buyer, changeSpendSecret, changeSalt);
113
+ const oldTree = this.tree(leaves), oldRoot = oldTree.root, oldIndex = BigInt(leaves.length), afterPayment = this.tree([...leaves, paymentCommitment]), finalTree = this.tree([...leaves, paymentCommitment, changeCommitment]), newRoot = finalTree.root, newIndex = oldIndex + 2n;
114
+ const statement = Buffer.concat([this.header(2), fieldBytes(dealCommitment), fieldBytes(dealNullifier), fieldBytes(inputNullifier), fieldBytes(paymentCommitment), fieldBytes(changeCommitment), u64(agreement.expiry), fieldBytes(oldRoot), u64(oldIndex), fieldBytes(newRoot), u64(newIndex)]);
115
+ const input = asStrings({ statement: [...statement], ...digestInputs(statement), dealId: agreement.dealId, revision: agreement.revision, transcriptRootLo: agreement.transcriptRootLo, transcriptRootHi: agreement.transcriptRootHi, serviceDigestLo: agreement.serviceDigestLo, serviceDigestHi: agreement.serviceDigestHi, settlementNonceLo: agreement.settlementNonceLo, settlementNonceHi: agreement.settlementNonceHi, paymentAmount: agreement.paymentAmount, blinding: agreement.blinding, buyerAx: buyer[0], buyerAy: buyer[1], sellerAx: seller[0], sellerAy: seller[1], recipientSpendTag: agreement.recipientSpendTag, buyerR8x: buyerSignature.R8x, buyerR8y: buyerSignature.R8y, buyerS: buyerSignature.S, sellerR8x: agreement.sellerSignature.R8x, sellerR8y: agreement.sellerSignature.R8y, sellerS: agreement.sellerSignature.S, inputAmount: inputNote.amount, inputSalt: inputNote.salt, inputSpendSecret: inputNote.spendSecret, inputLeafIndex: inputNote.leafIndex, inputPath: oldTree.path(Number(inputNote.leafIndex)).siblings, changeAmount, changeSpendSecret, changeSalt, paymentAppendPath: oldTree.appendPath(), changeAppendPath: afterPayment.appendPath() });
116
+ return { input, statement, newRoot, newIndex, dealCommitment, dealNullifier, inputNullifier, paymentCommitment, changeCommitment, paymentSalt, changeOpening: { deployment: this.deploymentField, amount: changeAmount, ownerAx: buyer[0], ownerAy: buyer[1], spendSecret: changeSpendSecret, salt: changeSalt, commitment: changeCommitment, leafIndex: oldIndex + 1n } };
117
+ }
118
+ buildWithdraw(leaves, note, recipient) {
119
+ const address = Buffer.from(recipient.replace(/^0x/, ""), "hex");
120
+ if (address.length !== 32)
121
+ fail("RECIPIENT_INVALID", "withdrawal recipient must be a 32-byte Sui address");
122
+ const tree = this.tree(leaves), commitment = this.noteCommitment(note.amount, [note.ownerAx, note.ownerAy], note.spendSecret, note.salt);
123
+ if (commitment !== note.commitment || leaves[Number(note.leafIndex)] !== commitment)
124
+ fail("NOTE_RECOVERY_MISMATCH", "withdrawal opening does not match indexed leaf");
125
+ const noteNullifier = this.poseidonHash([2011n, this.deploymentField, note.spendSecret, commitment]);
126
+ const statement = Buffer.concat([this.header(3), fieldBytes(tree.root), fieldBytes(noteNullifier), address, u64(note.amount)]);
127
+ const input = asStrings({ statement: [...statement], ...digestInputs(statement), ownerAx: note.ownerAx, ownerAy: note.ownerAy, spendSecret: note.spendSecret, salt: note.salt, leafIndex: note.leafIndex, pathElements: tree.path(Number(note.leafIndex)).siblings });
128
+ return { input, statement, noteNullifier };
129
+ }
130
+ buildWithdrawV2(leaves, note, recipient, amount, exactExit = false, changeSpendSecret = scalar(), changeSalt = scalar()) {
131
+ if (this.deployment.verifierVersion !== 2)
132
+ fail("POOL_VERSION_INVALID", "v2 withdrawals require a v2 deployment");
133
+ if (amount <= 0n || amount > note.amount || (!exactExit && amount !== 1000000n))
134
+ fail("AMOUNT_INVALID", "standard exit is 1,000,000 MIST; an exact exit may reveal another amount");
135
+ if (!/^0x[0-9a-f]{64}$/i.test(recipient))
136
+ fail("RECIPIENT_INVALID", "withdrawal recipient must be a 32-byte Sui address");
137
+ const tree = this.tree(leaves), commitment = this.noteCommitment(note.amount, [note.ownerAx, note.ownerAy], note.spendSecret, note.salt);
138
+ if (commitment !== note.commitment || leaves[Number(note.leafIndex)] !== commitment)
139
+ fail("NOTE_RECOVERY_MISMATCH", "withdrawal opening does not match indexed leaf");
140
+ const changeAmount = note.amount - amount, leafIndex = BigInt(leaves.length);
141
+ const changeCommitment = this.noteCommitment(changeAmount, [note.ownerAx, note.ownerAy], changeSpendSecret, changeSalt);
142
+ const nextTree = this.tree([...leaves, changeCommitment]), newRoot = nextTree.root, newIndex = leafIndex + 1n;
143
+ const noteNullifier = this.poseidonHash([2011n, this.deploymentField, note.spendSecret, commitment]);
144
+ const address = Buffer.from(recipient.slice(2), "hex");
145
+ const statement = Buffer.concat([this.header(3), fieldBytes(tree.root), fieldBytes(noteNullifier), address, u64(amount), Buffer.from([Number(exactExit)]), fieldBytes(changeCommitment), u64(leafIndex), fieldBytes(newRoot), u64(newIndex)]);
146
+ const input = asStrings({ statement: [...statement], ...digestInputs(statement), ownerAx: note.ownerAx, ownerAy: note.ownerAy, inputAmount: note.amount, spendSecret: note.spendSecret, salt: note.salt, leafIndex: note.leafIndex, pathElements: tree.path(Number(note.leafIndex)).siblings, changeAmount, changeSpendSecret, changeSalt, appendSiblings: tree.appendPath() });
147
+ return { input, statement, noteNullifier, newRoot, newIndex, changeOpening: { deployment: this.deploymentField, amount: changeAmount, ownerAx: note.ownerAx, ownerAy: note.ownerAy, spendSecret: changeSpendSecret, salt: changeSalt, commitment: changeCommitment, leafIndex }, exactExit, amount };
148
+ }
149
+ }
@@ -0,0 +1,28 @@
1
+ export type CircuitName = "deposit" | "transfer" | "withdraw";
2
+ export interface ProverConfig {
3
+ artifactRoot: string;
4
+ snarkjsPath: string;
5
+ converterPath: string;
6
+ rapidsnarkPath?: string;
7
+ version?: 2;
8
+ expected: Record<CircuitName, {
9
+ zkeySha256: string;
10
+ verifyingKeySha256: string;
11
+ }>;
12
+ }
13
+ export interface ProofResult {
14
+ proof: Buffer;
15
+ publicInputs: Buffer;
16
+ engine: "rapidsnark" | "snarkjs";
17
+ proveMs: number;
18
+ }
19
+ export declare class Groth16Prover {
20
+ private readonly config;
21
+ constructor(config: ProverConfig);
22
+ private artifactName;
23
+ doctor(): {
24
+ rapidSnark: boolean;
25
+ artifacts: Record<CircuitName, string>;
26
+ };
27
+ prove(name: CircuitName, input: Record<string, unknown>): Promise<ProofResult>;
28
+ }
package/dist/prover.js ADDED
@@ -0,0 +1,70 @@
1
+ import { execFile } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join, resolve } from "node:path";
6
+ import { promisify } from "node:util";
7
+ import { fail } from "./errors.js";
8
+ const run = promisify(execFile);
9
+ const sha256 = (path) => createHash("sha256").update(readFileSync(path)).digest("hex");
10
+ function executable(path) { try {
11
+ accessSync(path, constants.X_OK);
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ } }
17
+ export class Groth16Prover {
18
+ config;
19
+ constructor(config) {
20
+ this.config = config;
21
+ }
22
+ artifactName(name) { return `${name}${this.config.version === 2 ? "_v2" : ""}`; }
23
+ doctor() {
24
+ const artifacts = {};
25
+ for (const name of ["deposit", "transfer", "withdraw"]) {
26
+ const artifact = this.artifactName(name);
27
+ const zkey = resolve(this.config.artifactRoot, `proofs/${artifact}/${artifact}.zkey`), vk = resolve(this.config.artifactRoot, `proofs/${artifact}/verifying-key.bin`), expected = this.config.expected[name];
28
+ if (!existsSync(zkey) || !existsSync(vk))
29
+ fail("PROVING_ARTIFACT_MISSING", `pinned ${name} artifacts are missing`);
30
+ if (sha256(zkey) !== expected.zkeySha256 || sha256(vk) !== expected.verifyingKeySha256)
31
+ fail("ARTIFACT_HASH_MISMATCH", `${name} artifact hash differs from deployment configuration`);
32
+ artifacts[name] = expected.zkeySha256;
33
+ }
34
+ if (!executable(this.config.snarkjsPath) || !executable(this.config.converterPath))
35
+ fail("PROVER_TOOL_MISSING", "SnarkJS or the Arkworks converter is not executable");
36
+ return { rapidSnark: Boolean(this.config.rapidsnarkPath && executable(this.config.rapidsnarkPath)), artifacts };
37
+ }
38
+ async prove(name, input) {
39
+ this.doctor();
40
+ const directory = mkdtempSync(join(tmpdir(), `supercuts-${name}-`));
41
+ try {
42
+ const artifact = this.artifactName(name);
43
+ const inputPath = join(directory, "input.json"), witness = join(directory, "witness.wtns"), proofJson = join(directory, "proof.json"), publicJson = join(directory, "public.json"), converted = join(directory, "converted");
44
+ writeFileSync(inputPath, JSON.stringify(input), { mode: 0o600 });
45
+ mkdirSync(converted, { mode: 0o700 });
46
+ const generator = resolve(this.config.artifactRoot, `${artifact}_js/generate_witness.js`), wasm = resolve(this.config.artifactRoot, `${artifact}_js/${artifact}.wasm`), zkey = resolve(this.config.artifactRoot, `proofs/${artifact}/${artifact}.zkey`), verificationKey = resolve(this.config.artifactRoot, `proofs/${artifact}/verification-key.json`);
47
+ if (![generator, wasm, verificationKey].every(existsSync))
48
+ fail("PROVING_ARTIFACT_MISSING", `${name} witness or verification artifacts are missing`);
49
+ await run(process.execPath, [generator, wasm, inputPath, witness], { timeout: 10 * 60_000, maxBuffer: 4 * 1024 * 1024 });
50
+ const useRapid = Boolean(this.config.rapidsnarkPath && executable(this.config.rapidsnarkPath)), started = Date.now();
51
+ if (useRapid)
52
+ await run(this.config.rapidsnarkPath, [zkey, witness, proofJson, publicJson], { timeout: 10 * 60_000, maxBuffer: 4 * 1024 * 1024 });
53
+ else
54
+ await run(this.config.snarkjsPath, ["groth16", "prove", zkey, witness, proofJson, publicJson], { timeout: 20 * 60_000, maxBuffer: 4 * 1024 * 1024 });
55
+ const proveMs = Date.now() - started;
56
+ await run(this.config.snarkjsPath, ["groth16", "verify", verificationKey, publicJson, proofJson], { timeout: 2 * 60_000, maxBuffer: 4 * 1024 * 1024 });
57
+ await run(this.config.converterPath, [name, verificationKey, proofJson, publicJson, converted], { timeout: 2 * 60_000, maxBuffer: 4 * 1024 * 1024 });
58
+ const preparedVk = join(converted, "verifying-key.bin");
59
+ if (sha256(preparedVk) !== this.config.expected[name].verifyingKeySha256)
60
+ fail("ARTIFACT_HASH_MISMATCH", "converter produced an unexpected verifying key");
61
+ const proof = readFileSync(join(converted, "proof.bin")), publicInputs = readFileSync(join(converted, "public-inputs.bin"));
62
+ if (!proof.length || publicInputs.length !== 64)
63
+ fail("PROOF_INVALID", "converted proof or public inputs have an invalid size");
64
+ return { proof, publicInputs, engine: useRapid ? "rapidsnark" : "snarkjs", proveMs };
65
+ }
66
+ finally {
67
+ rmSync(directory, { recursive: true, force: true });
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,32 @@
1
+ import { RoleStore } from "./store.js";
2
+ export interface RelayRequest {
3
+ operationId: string;
4
+ expiresAt: number;
5
+ nonce: string;
6
+ buyerAddress: string;
7
+ challengeSignature: string;
8
+ packageId: string;
9
+ poolId: string;
10
+ coinType: string;
11
+ functionName: "transfer_private";
12
+ publicStatement: Uint8Array;
13
+ proof: Uint8Array;
14
+ verifierVersion: number;
15
+ }
16
+ export interface RelayPin {
17
+ packageId: string;
18
+ poolId: string;
19
+ coinType: string;
20
+ verifierVersion: number;
21
+ }
22
+ export type ChallengeVerifier = (address: string, challenge: Uint8Array, signature: string) => Promise<boolean>;
23
+ export type ProofVerifier = (statement: Uint8Array, proof: Uint8Array) => Promise<boolean>;
24
+ export declare function relayChallenge(request: Omit<RelayRequest, "challengeSignature">): Uint8Array;
25
+ export declare class RelayAdmission {
26
+ private readonly store;
27
+ private readonly pin;
28
+ private readonly verifyChallenge;
29
+ private readonly verifyProof;
30
+ constructor(store: RoleStore, pin: RelayPin, verifyChallenge: ChallengeVerifier, verifyProof: ProofVerifier);
31
+ admit(request: RelayRequest, now?: number): Promise<void>;
32
+ }
@@ -0,0 +1,43 @@
1
+ import { createHash } from "node:crypto";
2
+ import { fail } from "./errors.js";
3
+ export function relayChallenge(request) {
4
+ const lengths = Buffer.alloc(8);
5
+ lengths.writeUInt32BE(request.publicStatement.length);
6
+ lengths.writeUInt32BE(request.proof.length, 4);
7
+ return Buffer.from(JSON.stringify({
8
+ domain: "supercuts-relay-v1", operationId: request.operationId, expiresAt: request.expiresAt, nonce: request.nonce,
9
+ buyerAddress: request.buyerAddress, packageId: request.packageId, poolId: request.poolId, coinType: request.coinType,
10
+ functionName: request.functionName, verifierVersion: request.verifierVersion,
11
+ payloadHash: createHash("sha256").update(lengths).update(request.publicStatement).update(request.proof).digest("hex"),
12
+ }));
13
+ }
14
+ export class RelayAdmission {
15
+ store;
16
+ pin;
17
+ verifyChallenge;
18
+ verifyProof;
19
+ constructor(store, pin, verifyChallenge, verifyProof) {
20
+ this.store = store;
21
+ this.pin = pin;
22
+ this.verifyChallenge = verifyChallenge;
23
+ this.verifyProof = verifyProof;
24
+ }
25
+ async admit(request, now = Date.now()) {
26
+ if (!request.operationId || !request.nonce || !request.buyerAddress || request.operationId.length > 256 || request.nonce.length > 256)
27
+ fail("RELAY_CHALLENGE_INVALID", "relay challenge identity is missing or too long");
28
+ if (!Number.isSafeInteger(request.expiresAt) || request.expiresAt - now > 10 * 60_000)
29
+ fail("RELAY_CHALLENGE_INVALID", "relay challenge expiry is invalid or too far in the future");
30
+ if (!request.publicStatement.length || request.publicStatement.length > 4096 || !request.proof.length || request.proof.length > 4096)
31
+ fail("RELAY_PAYLOAD_INVALID", "relay proof or statement size is invalid");
32
+ if (request.expiresAt <= now)
33
+ fail("RELAY_CHALLENGE_EXPIRED", "relay challenge has expired");
34
+ if (request.packageId !== this.pin.packageId || request.poolId !== this.pin.poolId || request.coinType !== this.pin.coinType || request.verifierVersion !== this.pin.verifierVersion || request.functionName !== "transfer_private")
35
+ fail("RELAY_TARGET_REJECTED", "relay request does not target the pinned pool function and verifier");
36
+ if (!(await this.verifyChallenge(request.buyerAddress, relayChallenge(request), request.challengeSignature)))
37
+ fail("RELAY_SIGNATURE_INVALID", "relay challenge signature is invalid");
38
+ if (!(await this.verifyProof(request.publicStatement, request.proof)))
39
+ fail("RELAY_PROOF_INVALID", "proof does not verify against its public statement");
40
+ if (!this.store.claimOnce("relay-nonce", `${request.buyerAddress}:${request.nonce}`, request.operationId))
41
+ fail("RELAY_REPLAY", "relay challenge nonce was already consumed");
42
+ }
43
+ }
@@ -0,0 +1,67 @@
1
+ import { type CapabilityManifest } from "./capability.js";
2
+ import { type AgentMandate } from "./mandate.js";
3
+ import type { Phase3App } from "./phase3.js";
4
+ interface Entry {
5
+ manifest: CapabilityManifest;
6
+ blobId: string;
7
+ signature: string;
8
+ transportPublicKey: string;
9
+ preview: Record<string, unknown>;
10
+ }
11
+ export declare class RemoteBuyer {
12
+ #private;
13
+ private readonly app;
14
+ private readonly mandate;
15
+ private readonly http;
16
+ constructor(app: Phase3App, mandate: AgentMandate, origin: string, http?: typeof fetch);
17
+ private request;
18
+ catalog(): Promise<{
19
+ capabilities: Entry[];
20
+ }>;
21
+ rankBriefs(input: {
22
+ budget: string;
23
+ topic: string;
24
+ }): Promise<Record<string, unknown>>;
25
+ negotiate(input: {
26
+ capabilityId: string;
27
+ sessionId: string;
28
+ runId: string;
29
+ task: string;
30
+ opening: string;
31
+ maximum: string;
32
+ briefHash?: string;
33
+ }): Promise<Record<string, unknown>>;
34
+ quote(input: {
35
+ capabilityId: string;
36
+ operationId: string;
37
+ requestId: string;
38
+ runId: string;
39
+ task: string;
40
+ briefHash?: string;
41
+ }): Promise<Record<string, unknown>>;
42
+ purchaseBrief(input: {
43
+ capabilityId: string;
44
+ operationId: string;
45
+ requestId: string;
46
+ runId: string;
47
+ task: string;
48
+ budget: string;
49
+ }): Promise<Record<string, unknown>>;
50
+ agreement(input: {
51
+ capabilityId: string;
52
+ operationId: string;
53
+ sessionId: string;
54
+ runId: string;
55
+ briefHash: string;
56
+ }): Promise<Record<string, unknown>>;
57
+ settleAnalysis(input: {
58
+ sessionId: string;
59
+ operationId: string;
60
+ }): Promise<Record<string, unknown>>;
61
+ delivery(input: {
62
+ sessionId: string;
63
+ operationId: string;
64
+ }): Promise<Record<string, unknown>>;
65
+ events(runId: string): Promise<Record<string, unknown>>;
66
+ }
67
+ export {};