@metoncash/sdk-v1
v0.3.0
Published
SDK to build **Groth16 proofs** for the Meton `deposit` / `withdraw` / `transfer` circuits, plus the curve/commitment/encrypted-note helpers needed to construct inputs and manage wallet state off-chain.
Readme
Meton SDK
SDK to build Groth16 proofs for the Meton deposit / withdraw / transfer
circuits, plus the curve/commitment/encrypted-note helpers needed to construct
inputs and manage wallet state off-chain.
Install
npm install @metoncash/sdk-v1
# peer runtime dep used for proving:
npm install snarkjsUsage
import { MetonSDK, deriveViewKey, randomScalar } from "@metoncash/sdk-v1";
// Point at a build directory laid out as <dir>/<c>/<c>_js/<c>.wasm + <dir>/<c>/<c>_final.zkey
const sdk = MetonSDK.fromBuildDir("./build");
// …or pass artifacts explicitly (paths or Uint8Array buffers):
// const sdk = new MetonSDK({
// deposit: { wasm, zkey },
// withdraw: { wasm, zkey },
// transfer: { wasm, zkey },
// });
// DEPOSIT — commit a public amount
const dep = await sdk.deposit({ amount: 1000n, accountId: 7n, seqNo: 1n });
// → { proof, publicSignals, commitment: [x,y], blinding } (persist `blinding`)
// WITHDRAW — prove balance ≥ amount
const w = await sdk.withdraw({
amount: 400n, balance: 1000n, oldBlind, accountId: 7n, seqNo: 2n,
});
// → { proof, publicSignals, newCommitment, remainder, newBlind }
// TRANSFER — hidden amount, encrypted to the receiver's VIEWING key
const recv = deriveViewKey(receiverSpendSecret); // recipient side
const t = await sdk.transfer({
amount: 300n, senderBalance: 1000n, senderOldBlind,
receiverViewPk: recv.pkView, accountId: 7n, seqNo: 3n,
});
// → { proof, publicSignals, newSenderCommitment, amountCommitment, note,
// remainder, senderNewBlind, amountBlind, ephScalar }
// note = { Rx, Ry, enc_amount, enc_blind } is the PUBLIC on-chain payload;
// ephScalar (and the blinds) are secrets — persist them, never publish them.Inputs are validated before any proving (64-bit amounts, amount ≤ balance, 252-bit
blinds, accountId in [0, 2²⁵⁶) — reduced mod r like the contract does — receiver
key on-curve and in the prime-order subgroup, …) and rejected with a RangeError naming
the field.
accountId is the wallet contract's address hash: accountIdOf(await minter.getWalletAddress(owner)).
Registering a wallet
import { generateWalletKeys, registerWitness } from "@metoncash/sdk-v1";
const keys = generateWalletKeys();
await minter.sendRegister(sender, toNano("0.1"), {
queryId: 0n,
publicViewKey: { $: "Point", x: keys.viewPk[0], y: keys.viewPk[1] },
witness: registerWitness(keys.viewPk), // proves the key is in the prime-order subgroup
});proof + publicSignals are the snarkjs Groth16 outputs. Every result also returns the secrets you must persist (blindings,
note) to keep your local opening of the commitment spendable.
Receiving / auditing
import { viewNote } from "@metoncash/sdk-v1";
// recipient (or a granted auditor) holding sk_view:
const seen = viewNote(recv.skView, note, amountCommitment[0], amountCommitment[1]);
// → { amount, amountBlind, valid } (valid: the note opens the on-chain amount_commit)What's exported
- Prover:
MetonSDK(deposit,withdraw,transfer,fromBuildDir). - Keys / notes:
generateWalletKeys,deriveViewKey,derivePub,registerWitness,accountIdOf,encryptNote,decryptNote,viewNote,sealNote(returns{ note, ephScalar }),openNote. - Curve / commitments:
commit,add,mul,mod,randomScalar,isOnCurve,isInPrimeSubgroup,cofactorWitness,Point,Gx/Gy/Hx/Hy,r,d,L. - Hash:
mimc5perm. Constants:CONSTANTS.
Notes
- Defaults:
blinding,newBlind,senderNewBlind,ephScalardefault to fresh randomness;amountBlinddefaults tooldBlind − newBlind (mod L)so thatold == new ⊕ amountalso holds as points (handy for local checks; the contract does not require it — value conservation is proven inside the circuit). The ephemeral scalar must be fresh per transfer — reuse leaks amounts. - Proving is CPU-heavy (transfer ≈ 40k constraints); run it off the UI thread / in a worker.
