@metoncash/sdk-v1 0.2.0 → 0.2.1

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.
package/README.md CHANGED
@@ -15,7 +15,7 @@ npm install snarkjs
15
15
  ## Usage
16
16
 
17
17
  ```ts
18
- import { MetonSDK, deriveViewKey, randomScalar } from "meton-cash/sdk-v1";
18
+ import { MetonSDK, deriveViewKey, randomScalar } from "@metoncash/sdk-v1";
19
19
 
20
20
  // Point at a build directory laid out as <dir>/<c>/<c>_js/<c>.wasm + <dir>/<c>/<c>_final.zkey
21
21
  const sdk = MetonSDK.fromBuildDir("./build");
@@ -45,6 +45,28 @@ const t = await sdk.transfer({
45
45
  });
46
46
  // → { proof, publicSignals, newSenderCommitment, amountCommitment, note,
47
47
  // remainder, senderNewBlind, amountBlind, ephScalar }
48
+ // note = { Rx, Ry, enc_amount, enc_blind } is the PUBLIC on-chain payload;
49
+ // ephScalar (and the blinds) are secrets — persist them, never publish them.
50
+ ```
51
+
52
+ Inputs are validated before any proving (64-bit amounts, `amount ≤ balance`, 252-bit
53
+ blinds, `accountId` in `[0, 2²⁵⁶)` — reduced mod `r` like the contract does — receiver
54
+ key on-curve and in the prime-order subgroup, …) and rejected with a `RangeError` naming
55
+ the field.
56
+
57
+ `accountId` is the wallet contract's address hash: `accountIdOf(await minter.getWalletAddress(owner))`.
58
+
59
+ ## Registering a wallet
60
+
61
+ ```ts
62
+ import { generateWalletKeys, registerWitness } from "@metoncash/sdk-v1";
63
+
64
+ const keys = generateWalletKeys();
65
+ await minter.sendRegister(sender, toNano("0.1"), {
66
+ queryId: 0n,
67
+ publicViewKey: { $: "Point", x: keys.viewPk[0], y: keys.viewPk[1] },
68
+ witness: registerWitness(keys.viewPk), // proves the key is in the prime-order subgroup
69
+ });
48
70
  ```
49
71
 
50
72
  `proof` + `publicSignals` are the snarkjs Groth16 outputs. Every result also returns the **secrets you must persist** (blindings,
@@ -53,7 +75,7 @@ note) to keep your local opening of the commitment spendable.
53
75
  ## Receiving / auditing
54
76
 
55
77
  ```ts
56
- import { viewNote } from "meton-cash/sdk-v1";
78
+ import { viewNote } from "@metoncash/sdk-v1";
57
79
 
58
80
  // recipient (or a granted auditor) holding sk_view:
59
81
  const seen = viewNote(recv.skView, note, amountCommitment[0], amountCommitment[1]);
@@ -63,14 +85,15 @@ const seen = viewNote(recv.skView, note, amountCommitment[0], amountCommitment[1
63
85
  ## What's exported
64
86
 
65
87
  - **Prover:** `MetonSDK` (`deposit`, `withdraw`, `transfer`, `fromBuildDir`).
66
- - **Keys / notes:** `deriveViewKey`, `derivePub`, `encryptNote`, `decryptNote`, `viewNote`.
67
- - **Curve / commitments:** `commit`, `add`, `mul`, `mod`, `randomScalar`, `Point`, `Gx/Gy/Hx/Hy`, `r`, `d`, `L`.
88
+ - **Keys / notes:** `generateWalletKeys`, `deriveViewKey`, `derivePub`, `registerWitness`, `accountIdOf`, `encryptNote`, `decryptNote`, `viewNote`, `sealNote` (returns `{ note, ephScalar }`), `openNote`.
89
+ - **Curve / commitments:** `commit`, `add`, `mul`, `mod`, `randomScalar`, `isOnCurve`, `isInPrimeSubgroup`, `cofactorWitness`, `Point`, `Gx/Gy/Hx/Hy`, `r`, `d`, `L`.
68
90
  - **Hash:** `mimc5perm`. **Constants:** `CONSTANTS`.
69
91
 
70
92
  ## Notes
71
93
 
72
94
  - **Defaults:** `blinding`, `newBlind`, `senderNewBlind`, `ephScalar` default to fresh
73
- randomness; `amountBlind` defaults to `oldBlind − newBlind (mod L)` (the homomorphic
74
- relation the contract checks). The **ephemeral scalar must be fresh per transfer**
75
- reuse leaks amounts.
76
- - Proving is CPU-heavy (transfer 30k constraints); run it off the UI thread / in a worker.
95
+ randomness; `amountBlind` defaults to `oldBlind − newBlind (mod L)` so that
96
+ `old == new amount` also holds as points (handy for local checks; the contract does
97
+ not require it — value conservation is proven inside the circuit). The **ephemeral
98
+ scalar must be fresh per transfer** reuse leaks amounts.
99
+ - Proving is CPU-heavy (transfer ≈ 40k constraints); run it off the UI thread / in a worker.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export { CONSTANTS } from "./constants";
2
- export { Point, r, d, L, Gx, Gy, Hx, Hy, mod, modpow, modinv, add, mul, commit, derivePub, randomScalar, } from "./jubjub";
2
+ export { Point, r, d, L, Gx, Gy, Hx, Hy, mod, modpow, modinv, add, mul, commit, derivePub, randomScalar, isOnCurve, isIdentity, isInPrimeSubgroup, cofactorWitness, } from "./jubjub";
3
3
  export { mimc5perm } from "./mimc";
4
4
  export { Note, deriveViewKey, encryptNote, decryptNote, viewNote } from "./note";
5
5
  export { MetonSDK, CircuitArtifacts, MetonConfig, Proof, DepositArgs, WithdrawArgs, TransferArgs, DepositResult, WithdrawResult, TransferResult, } from "./prover";
6
- export { WalletKeys, generateWalletKeys, walletKeysFromSpend, viewKeyFromSpend, SealNoteArgs, SealedNote, sealNote, openNote, } from "./utils";
6
+ export { WalletKeys, generateWalletKeys, walletKeysFromSpend, viewKeyFromSpend, SealNoteArgs, SealedNote, sealNote, openNote, accountIdOf, registerWitness, } from "./utils";
7
7
  export { Wallet, Minter, Factory } from "./wrappers";
8
+ export type { SubgroupWitness, CellRef, Register } from "./wrappers/Minter.gen";
package/dist/index.js CHANGED
@@ -1,6 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Factory = exports.Minter = exports.Wallet = exports.openNote = exports.sealNote = exports.viewKeyFromSpend = exports.walletKeysFromSpend = exports.generateWalletKeys = exports.MetonSDK = exports.viewNote = exports.decryptNote = exports.encryptNote = exports.deriveViewKey = exports.mimc5perm = exports.randomScalar = exports.derivePub = exports.commit = exports.mul = exports.add = exports.modinv = exports.modpow = exports.mod = exports.Hy = exports.Hx = exports.Gy = exports.Gx = exports.L = exports.d = exports.r = exports.CONSTANTS = void 0;
3
+ exports.Factory = exports.Minter = exports.Wallet = exports.registerWitness = exports.accountIdOf = exports.openNote = exports.sealNote = exports.viewKeyFromSpend = exports.walletKeysFromSpend = exports.generateWalletKeys = exports.MetonSDK = exports.viewNote = exports.decryptNote = exports.encryptNote = exports.deriveViewKey = exports.mimc5perm = exports.cofactorWitness = exports.isInPrimeSubgroup = exports.isIdentity = exports.isOnCurve = exports.randomScalar = exports.derivePub = exports.commit = exports.mul = exports.add = exports.modinv = exports.modpow = exports.mod = exports.Hy = exports.Hx = exports.Gy = exports.Gx = exports.L = exports.d = exports.r = exports.CONSTANTS = void 0;
4
+ // index.ts - public surface of @metoncash/sdk-v1.
5
+ //
6
+ // constants generated curve / MiMC parameters shared with circuits and contracts
7
+ // jubjub field + curve arithmetic, Pedersen commitments, subgroup checks
8
+ // mimc the MiMC-5 permutation used for note masks and view-key derivation
9
+ // note ECDH note encryption / decryption and view-key derivation
10
+ // prover MetonSDK - Groth16 proof building for deposit / withdraw / transfer
11
+ // utils wallet keys, Register witness, sealNote/openNote, accountIdOf
12
+ // wrappers generated contract wrappers (Wallet, Minter, Factory)
13
+ //
14
+ // Typical flow: generateWalletKeys -> Minter.Register(viewPk, registerWitness(viewPk))
15
+ // -> accountIdOf(walletAddress) -> MetonSDK.deposit/transfer/withdraw -> the receiver
16
+ // opens incoming notes with viewNote(viewSk, note, amountCommitment).
4
17
  var constants_1 = require("./constants");
5
18
  Object.defineProperty(exports, "CONSTANTS", { enumerable: true, get: function () { return constants_1.CONSTANTS; } });
6
19
  var jubjub_1 = require("./jubjub");
@@ -19,6 +32,10 @@ Object.defineProperty(exports, "mul", { enumerable: true, get: function () { ret
19
32
  Object.defineProperty(exports, "commit", { enumerable: true, get: function () { return jubjub_1.commit; } });
20
33
  Object.defineProperty(exports, "derivePub", { enumerable: true, get: function () { return jubjub_1.derivePub; } });
21
34
  Object.defineProperty(exports, "randomScalar", { enumerable: true, get: function () { return jubjub_1.randomScalar; } });
35
+ Object.defineProperty(exports, "isOnCurve", { enumerable: true, get: function () { return jubjub_1.isOnCurve; } });
36
+ Object.defineProperty(exports, "isIdentity", { enumerable: true, get: function () { return jubjub_1.isIdentity; } });
37
+ Object.defineProperty(exports, "isInPrimeSubgroup", { enumerable: true, get: function () { return jubjub_1.isInPrimeSubgroup; } });
38
+ Object.defineProperty(exports, "cofactorWitness", { enumerable: true, get: function () { return jubjub_1.cofactorWitness; } });
22
39
  var mimc_1 = require("./mimc");
23
40
  Object.defineProperty(exports, "mimc5perm", { enumerable: true, get: function () { return mimc_1.mimc5perm; } });
24
41
  var note_1 = require("./note");
@@ -34,7 +51,9 @@ Object.defineProperty(exports, "walletKeysFromSpend", { enumerable: true, get: f
34
51
  Object.defineProperty(exports, "viewKeyFromSpend", { enumerable: true, get: function () { return utils_1.viewKeyFromSpend; } });
35
52
  Object.defineProperty(exports, "sealNote", { enumerable: true, get: function () { return utils_1.sealNote; } });
36
53
  Object.defineProperty(exports, "openNote", { enumerable: true, get: function () { return utils_1.openNote; } });
37
- // Contract wrappers deploy/init Wallet, Minter, Factory through the SDK.
54
+ Object.defineProperty(exports, "accountIdOf", { enumerable: true, get: function () { return utils_1.accountIdOf; } });
55
+ Object.defineProperty(exports, "registerWitness", { enumerable: true, get: function () { return utils_1.registerWitness; } });
56
+ // Contract wrappers - deploy/init Wallet, Minter, Factory through the SDK.
38
57
  var wrappers_1 = require("./wrappers");
39
58
  Object.defineProperty(exports, "Wallet", { enumerable: true, get: function () { return wrappers_1.Wallet; } });
40
59
  Object.defineProperty(exports, "Minter", { enumerable: true, get: function () { return wrappers_1.Minter; } });
package/dist/jubjub.d.ts CHANGED
@@ -1,25 +1,129 @@
1
1
  /**
2
- * jubjub.ts Jubjub twisted-Edwards curve arithmetic (a = -1) over the
2
+ * jubjub.ts - Jubjub twisted-Edwards curve arithmetic (a = -1) over the
3
3
  * BLS12-381 scalar field. Mirrors circuits/lib for off-chain input building.
4
+ *
5
+ * Curve: -x^2 + y^2 = 1 + d*x^2*y^2 over F_r. The group has order 8*L (cofactor
6
+ * 8, prime L); G and H generate the order-L subgroup and are the same points the
7
+ * circuits (generated_constants.circom) and the contracts (twEdwards.tolk) use.
8
+ *
9
+ * Everything here is plain bigint arithmetic and NOT constant-time. It is
10
+ * intended for a client that already holds the secrets it operates on; do not
11
+ * run it in a context where timing is observable by an adversary.
4
12
  */
13
+ /** An affine Jubjub point [x, y]; the identity is [0, 1]. */
5
14
  export type Point = [bigint, bigint];
15
+ /** The BLS12-381 scalar field modulus r (the coordinate field of Jubjub). */
6
16
  export declare const r: bigint;
17
+ /** Twisted-Edwards curve coefficient d (a is fixed to -1). */
7
18
  export declare const d: bigint;
19
+ /** Prime order of the Jubjub subgroup generated by G and H (252 bits). */
8
20
  export declare const L: bigint;
21
+ /** x of the value generator G (Pedersen: value*G + blinding*H). */
9
22
  export declare const Gx: bigint;
23
+ /** y of the value generator G. */
10
24
  export declare const Gy: bigint;
25
+ /** x of the blinding generator H (nothing-up-my-sleeve, independent of G). */
11
26
  export declare const Hx: bigint;
27
+ /** y of the blinding generator H. */
12
28
  export declare const Hy: bigint;
29
+ /**
30
+ * Least non-negative residue of `a` modulo `m` (default: the field modulus r).
31
+ * Unlike `%`, the result is never negative.
32
+ */
13
33
  export declare const mod: (a: bigint, m?: bigint) => bigint;
34
+ /**
35
+ * Modular exponentiation `base^exp mod m` by square-and-multiply.
36
+ * @param base any integer (reduced mod m first)
37
+ * @param exp non-negative exponent
38
+ * @param m modulus, default r
39
+ */
14
40
  export declare function modpow(base: bigint, exp: bigint, m?: bigint): bigint;
41
+ /**
42
+ * Modular inverse by Fermat: `a^(m-2) mod m`. Only valid for PRIME `m` (r or L)
43
+ * and `a != 0 mod m`; for a == 0 it silently returns 0.
44
+ */
15
45
  export declare const modinv: (a: bigint, m?: bigint) => bigint;
16
- /** Complete twisted-Edwards addition (a = -1). */
46
+ /**
47
+ * Complete twisted-Edwards addition (a = -1): (x1, y1) (+) (x2, y2).
48
+ * The formula is complete on Jubjub (d is a non-square), so it is correct for
49
+ * every pair of curve points, including doubling and adding the identity;
50
+ * the denominators are never zero. Coordinates need not be reduced.
51
+ * @returns the sum, reduced mod r
52
+ */
17
53
  export declare function add(x1: bigint, y1: bigint, x2: bigint, y2: bigint): Point;
18
- /** Scalar multiplication: out = scalar · (x, y). */
54
+ /**
55
+ * Scalar multiplication: out = s * (x, y) by double-and-add.
56
+ *
57
+ * `s` is reduced mod L first, so the result is only meaningful for points of the
58
+ * order-L subgroup (for a torsion point, [s]P and [s mod L]P differ). Two
59
+ * consequences: [L]*P == identity always holds here and CANNOT be used as a
60
+ * subgroup test (see isInPrimeSubgroup), and negative or over-sized scalars
61
+ * are accepted. Not constant-time.
62
+ */
19
63
  export declare function mul(x: bigint, y: bigint, s: bigint): Point;
20
- /** Pedersen commitment: value·G + blinding·H. */
64
+ /**
65
+ * Pedersen commitment: value*G (+) blinding*H.
66
+ *
67
+ * Matches PedersenCommit in circuits/lib/pedersen.circom. Homomorphic:
68
+ * commit(a, b1) (+) commit(c, b2) == commit(a + c, b1 + b2 mod L), which is what
69
+ * lets a client re-check old == new (+) amount after a transfer.
70
+ * @param value token amount; the circuits bound it to 64 bits, this helper does not
71
+ * @param blinding blinding factor; effectively taken mod L
72
+ */
21
73
  export declare function commit(value: bigint, blinding: bigint): Point;
22
- /** Public key for a secret scalar: pk = sk · G. */
74
+ /**
75
+ * Public key for a secret scalar: pk = sk * G. Always a subgroup point; it is
76
+ * the identity iff sk == 0 mod L (deriveViewKey / randomScalar never yield that).
77
+ */
23
78
  export declare function derivePub(sk: bigint): Point;
24
- /** Uniform random scalar in [1, 2^maxBits) reduced into the subgroup. */
79
+ /**
80
+ * Uniform random scalar in [1, L) (L is 252 bits, so it also fits `maxBits` >= 252).
81
+ * 64 random bytes are reduced mod L: with 512 input bits the modulo bias is
82
+ * < 2^-260, whereas reducing 256 bits mod the 252-bit L skews residues by ~6%.
83
+ *
84
+ * Uses crypto.randomBytes (CSPRNG). This is the source for every secret the SDK
85
+ * generates: spend keys, blinding factors and the per-note ephemeral scalar k.
86
+ * @param maxBits if < 252 the result is truncated to its low `maxBits` bits
87
+ * (result in [1, 2^maxBits)); >= 252 is a no-op since L < 2^252
88
+ * @returns a scalar that is never 0 (0 is mapped to 1)
89
+ */
25
90
  export declare function randomScalar(maxBits?: number): bigint;
91
+ /**
92
+ * Twisted-Edwards membership: -x^2 + y^2 == 1 + d*x^2*y^2 (coordinates reduced
93
+ * mod r first). On-curve alone says nothing about the subgroup: Jubjub has
94
+ * cofactor 8, so use isInPrimeSubgroup for anything that will be multiplied by
95
+ * a secret scalar.
96
+ */
97
+ export declare function isOnCurve(x: bigint, y: bigint): boolean;
98
+ /** True iff (x, y) is the group identity (0, 1), after reduction mod r. */
99
+ export declare const isIdentity: (x: bigint, y: bigint) => boolean;
100
+ /**
101
+ * Cofactor witness for a prime-order-subgroup point P: Q = [8^-1 mod L]*P, [2]Q, [4]Q.
102
+ * The contract (Register) and the circuit (transfer) verify [8]*Q == P instead of
103
+ * running a 252-step [L]*P ladder. Only meaningful for a subgroup point - for any
104
+ * other on-curve point [8]*Q != P (see isInPrimeSubgroup).
105
+ *
106
+ * The verifiers check q on-curve, q2 == q (+) q, q4 == q2 (+) q2 and
107
+ * P == q4 (+) q4; the Minter uses verifySum (division-free), the circuit
108
+ * AssertInSubgroup. Since the Edwards law is complete each sum is unique, so
109
+ * the chain pins [8]Q. Neither verifier re-derives Q, it must be supplied.
110
+ * @param x,y the point P; NOT validated here (callers validate first)
111
+ * @returns q (the Register/transfer witness) plus its doublings q2, q4
112
+ */
113
+ export declare function cofactorWitness(x: bigint, y: bigint): {
114
+ q: Point;
115
+ q2: Point;
116
+ q4: Point;
117
+ };
118
+ /**
119
+ * True iff (x, y) is on-curve AND in the prime-order subgroup. Jubjub has cofactor 8:
120
+ * the group is Z/8 x Z/L, so [8]*(any curve point) lies in the order-L subgroup and
121
+ * [8]*([8^-1 mod L]*P) == P holds exactly when P has no torsion component.
122
+ * (mul() reduces scalars mod L, so [L]*P == O cannot be used as the test.)
123
+ *
124
+ * The identity IS a subgroup point and passes; callers that need a non-trivial
125
+ * key (decryptNote, registerWitness, the transfer prover) check isIdentity too.
126
+ * Coordinates are reduced mod r, so non-canonical inputs (x + r) also pass;
127
+ * callers that store or compare points check canonical range separately.
128
+ */
129
+ export declare function isInPrimeSubgroup(x: bigint, y: bigint): boolean;
package/dist/jubjub.js CHANGED
@@ -1,7 +1,15 @@
1
1
  "use strict";
2
2
  /**
3
- * jubjub.ts Jubjub twisted-Edwards curve arithmetic (a = -1) over the
3
+ * jubjub.ts - Jubjub twisted-Edwards curve arithmetic (a = -1) over the
4
4
  * BLS12-381 scalar field. Mirrors circuits/lib for off-chain input building.
5
+ *
6
+ * Curve: -x^2 + y^2 = 1 + d*x^2*y^2 over F_r. The group has order 8*L (cofactor
7
+ * 8, prime L); G and H generate the order-L subgroup and are the same points the
8
+ * circuits (generated_constants.circom) and the contracts (twEdwards.tolk) use.
9
+ *
10
+ * Everything here is plain bigint arithmetic and NOT constant-time. It is
11
+ * intended for a client that already holds the secrets it operates on; do not
12
+ * run it in a context where timing is observable by an adversary.
5
13
  */
6
14
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
15
  if (k2 === undefined) k2 = k;
@@ -37,24 +45,44 @@ var __importStar = (this && this.__importStar) || (function () {
37
45
  };
38
46
  })();
39
47
  Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.modinv = exports.mod = exports.Hy = exports.Hx = exports.Gy = exports.Gx = exports.L = exports.d = exports.r = void 0;
48
+ exports.isIdentity = exports.modinv = exports.mod = exports.Hy = exports.Hx = exports.Gy = exports.Gx = exports.L = exports.d = exports.r = void 0;
41
49
  exports.modpow = modpow;
42
50
  exports.add = add;
43
51
  exports.mul = mul;
44
52
  exports.commit = commit;
45
53
  exports.derivePub = derivePub;
46
54
  exports.randomScalar = randomScalar;
55
+ exports.isOnCurve = isOnCurve;
56
+ exports.cofactorWitness = cofactorWitness;
57
+ exports.isInPrimeSubgroup = isInPrimeSubgroup;
47
58
  const crypto = __importStar(require("crypto"));
48
59
  const constants_1 = require("./constants");
60
+ /** The BLS12-381 scalar field modulus r (the coordinate field of Jubjub). */
49
61
  exports.r = BigInt(constants_1.CONSTANTS.FIELD_ORDER);
62
+ /** Twisted-Edwards curve coefficient d (a is fixed to -1). */
50
63
  exports.d = BigInt(constants_1.CONSTANTS.JUBJUB_D);
64
+ /** Prime order of the Jubjub subgroup generated by G and H (252 bits). */
51
65
  exports.L = BigInt(constants_1.CONSTANTS.JUBJUB_L);
66
+ /** x of the value generator G (Pedersen: value*G + blinding*H). */
52
67
  exports.Gx = BigInt(constants_1.CONSTANTS.GEN_Gx);
68
+ /** y of the value generator G. */
53
69
  exports.Gy = BigInt(constants_1.CONSTANTS.GEN_Gy);
70
+ /** x of the blinding generator H (nothing-up-my-sleeve, independent of G). */
54
71
  exports.Hx = BigInt(constants_1.CONSTANTS.GEN_Hx);
72
+ /** y of the blinding generator H. */
55
73
  exports.Hy = BigInt(constants_1.CONSTANTS.GEN_Hy);
74
+ /**
75
+ * Least non-negative residue of `a` modulo `m` (default: the field modulus r).
76
+ * Unlike `%`, the result is never negative.
77
+ */
56
78
  const mod = (a, m = exports.r) => ((a % m) + m) % m;
57
79
  exports.mod = mod;
80
+ /**
81
+ * Modular exponentiation `base^exp mod m` by square-and-multiply.
82
+ * @param base any integer (reduced mod m first)
83
+ * @param exp non-negative exponent
84
+ * @param m modulus, default r
85
+ */
58
86
  function modpow(base, exp, m = exports.r) {
59
87
  base = (0, exports.mod)(base, m);
60
88
  let result = 1n;
@@ -66,9 +94,19 @@ function modpow(base, exp, m = exports.r) {
66
94
  }
67
95
  return result;
68
96
  }
97
+ /**
98
+ * Modular inverse by Fermat: `a^(m-2) mod m`. Only valid for PRIME `m` (r or L)
99
+ * and `a != 0 mod m`; for a == 0 it silently returns 0.
100
+ */
69
101
  const modinv = (a, m = exports.r) => modpow((0, exports.mod)(a, m), m - 2n, m);
70
102
  exports.modinv = modinv;
71
- /** Complete twisted-Edwards addition (a = -1). */
103
+ /**
104
+ * Complete twisted-Edwards addition (a = -1): (x1, y1) (+) (x2, y2).
105
+ * The formula is complete on Jubjub (d is a non-square), so it is correct for
106
+ * every pair of curve points, including doubling and adding the identity;
107
+ * the denominators are never zero. Coordinates need not be reduced.
108
+ * @returns the sum, reduced mod r
109
+ */
72
110
  function add(x1, y1, x2, y2) {
73
111
  const x1x2 = (0, exports.mod)(x1 * x2);
74
112
  const y1y2 = (0, exports.mod)(y1 * y2);
@@ -81,7 +119,15 @@ function add(x1, y1, x2, y2) {
81
119
  const yden = (0, exports.mod)(1n + exports.r - dx);
82
120
  return [(0, exports.mod)(xnum * (0, exports.modinv)(xden)), (0, exports.mod)(ynum * (0, exports.modinv)(yden))];
83
121
  }
84
- /** Scalar multiplication: out = scalar · (x, y). */
122
+ /**
123
+ * Scalar multiplication: out = s * (x, y) by double-and-add.
124
+ *
125
+ * `s` is reduced mod L first, so the result is only meaningful for points of the
126
+ * order-L subgroup (for a torsion point, [s]P and [s mod L]P differ). Two
127
+ * consequences: [L]*P == identity always holds here and CANNOT be used as a
128
+ * subgroup test (see isInPrimeSubgroup), and negative or over-sized scalars
129
+ * are accepted. Not constant-time.
130
+ */
85
131
  function mul(x, y, s) {
86
132
  s = (0, exports.mod)(s, exports.L);
87
133
  let rx = 0n;
@@ -96,21 +142,42 @@ function mul(x, y, s) {
96
142
  }
97
143
  return [rx, ry];
98
144
  }
99
- /** Pedersen commitment: value·G + blinding·H. */
145
+ /**
146
+ * Pedersen commitment: value*G (+) blinding*H.
147
+ *
148
+ * Matches PedersenCommit in circuits/lib/pedersen.circom. Homomorphic:
149
+ * commit(a, b1) (+) commit(c, b2) == commit(a + c, b1 + b2 mod L), which is what
150
+ * lets a client re-check old == new (+) amount after a transfer.
151
+ * @param value token amount; the circuits bound it to 64 bits, this helper does not
152
+ * @param blinding blinding factor; effectively taken mod L
153
+ */
100
154
  function commit(value, blinding) {
101
155
  const [ax, ay] = mul(exports.Gx, exports.Gy, value);
102
156
  const [bx, by] = mul(exports.Hx, exports.Hy, blinding);
103
157
  return add(ax, ay, bx, by);
104
158
  }
105
- /** Public key for a secret scalar: pk = sk · G. */
159
+ /**
160
+ * Public key for a secret scalar: pk = sk * G. Always a subgroup point; it is
161
+ * the identity iff sk == 0 mod L (deriveViewKey / randomScalar never yield that).
162
+ */
106
163
  function derivePub(sk) {
107
164
  return mul(exports.Gx, exports.Gy, sk);
108
165
  }
109
- /** Uniform random scalar in [1, 2^maxBits) reduced into the subgroup. */
166
+ /**
167
+ * Uniform random scalar in [1, L) (L is 252 bits, so it also fits `maxBits` >= 252).
168
+ * 64 random bytes are reduced mod L: with 512 input bits the modulo bias is
169
+ * < 2^-260, whereas reducing 256 bits mod the 252-bit L skews residues by ~6%.
170
+ *
171
+ * Uses crypto.randomBytes (CSPRNG). This is the source for every secret the SDK
172
+ * generates: spend keys, blinding factors and the per-note ephemeral scalar k.
173
+ * @param maxBits if < 252 the result is truncated to its low `maxBits` bits
174
+ * (result in [1, 2^maxBits)); >= 252 is a no-op since L < 2^252
175
+ * @returns a scalar that is never 0 (0 is mapped to 1)
176
+ */
110
177
  function randomScalar(maxBits = 252) {
111
- const bytes = crypto.randomBytes(32);
178
+ const bytes = crypto.randomBytes(64);
112
179
  let s = 0n;
113
- for (let i = 0; i < 32; i++)
180
+ for (let i = 0; i < 64; i++)
114
181
  s = (s << 8n) | BigInt(bytes[i]);
115
182
  s = (0, exports.mod)(s, exports.L);
116
183
  const mask = (1n << BigInt(maxBits)) - 1n;
@@ -119,3 +186,58 @@ function randomScalar(maxBits = 252) {
119
186
  s = 1n;
120
187
  return s;
121
188
  }
189
+ /**
190
+ * Twisted-Edwards membership: -x^2 + y^2 == 1 + d*x^2*y^2 (coordinates reduced
191
+ * mod r first). On-curve alone says nothing about the subgroup: Jubjub has
192
+ * cofactor 8, so use isInPrimeSubgroup for anything that will be multiplied by
193
+ * a secret scalar.
194
+ */
195
+ function isOnCurve(x, y) {
196
+ x = (0, exports.mod)(x);
197
+ y = (0, exports.mod)(y);
198
+ const x2 = (0, exports.mod)(x * x);
199
+ const y2 = (0, exports.mod)(y * y);
200
+ return (0, exports.mod)(y2 - x2 - (0, exports.mod)(exports.d * (0, exports.mod)(x2 * y2))) === 1n;
201
+ }
202
+ /** True iff (x, y) is the group identity (0, 1), after reduction mod r. */
203
+ const isIdentity = (x, y) => (0, exports.mod)(x) === 0n && (0, exports.mod)(y) === 1n;
204
+ exports.isIdentity = isIdentity;
205
+ // 8^-1 mod L: multiplying a subgroup point by it "undoes" the cofactor.
206
+ const INV8 = (0, exports.modinv)(8n, exports.L);
207
+ /**
208
+ * Cofactor witness for a prime-order-subgroup point P: Q = [8^-1 mod L]*P, [2]Q, [4]Q.
209
+ * The contract (Register) and the circuit (transfer) verify [8]*Q == P instead of
210
+ * running a 252-step [L]*P ladder. Only meaningful for a subgroup point - for any
211
+ * other on-curve point [8]*Q != P (see isInPrimeSubgroup).
212
+ *
213
+ * The verifiers check q on-curve, q2 == q (+) q, q4 == q2 (+) q2 and
214
+ * P == q4 (+) q4; the Minter uses verifySum (division-free), the circuit
215
+ * AssertInSubgroup. Since the Edwards law is complete each sum is unique, so
216
+ * the chain pins [8]Q. Neither verifier re-derives Q, it must be supplied.
217
+ * @param x,y the point P; NOT validated here (callers validate first)
218
+ * @returns q (the Register/transfer witness) plus its doublings q2, q4
219
+ */
220
+ function cofactorWitness(x, y) {
221
+ const q = mul(x, y, INV8);
222
+ const q2 = add(q[0], q[1], q[0], q[1]);
223
+ const q4 = add(q2[0], q2[1], q2[0], q2[1]);
224
+ return { q, q2, q4 };
225
+ }
226
+ /**
227
+ * True iff (x, y) is on-curve AND in the prime-order subgroup. Jubjub has cofactor 8:
228
+ * the group is Z/8 x Z/L, so [8]*(any curve point) lies in the order-L subgroup and
229
+ * [8]*([8^-1 mod L]*P) == P holds exactly when P has no torsion component.
230
+ * (mul() reduces scalars mod L, so [L]*P == O cannot be used as the test.)
231
+ *
232
+ * The identity IS a subgroup point and passes; callers that need a non-trivial
233
+ * key (decryptNote, registerWitness, the transfer prover) check isIdentity too.
234
+ * Coordinates are reduced mod r, so non-canonical inputs (x + r) also pass;
235
+ * callers that store or compare points check canonical range separately.
236
+ */
237
+ function isInPrimeSubgroup(x, y) {
238
+ if (!isOnCurve(x, y))
239
+ return false;
240
+ const { q4 } = cofactorWitness(x, y);
241
+ const [ex, ey] = add(q4[0], q4[1], q4[0], q4[1]);
242
+ return ex === (0, exports.mod)(x) && ey === (0, exports.mod)(y);
243
+ }
package/dist/mimc.d.ts CHANGED
@@ -1,4 +1,19 @@
1
1
  /**
2
- * mimc.ts MiMC-5 permutation over the BLS12-381 scalar field.
2
+ * mimc.ts - MiMC-5 permutation over the BLS12-381 scalar field.
3
+ *
4
+ * Bit-for-bit the same function as MiMC5Perm in circuits/lib/mimc.circom:
5
+ * 110 rounds of x -> (x + key + c_i)^5, output = final state + key. The round
6
+ * constants come from the generated constants table shared with the circuit.
7
+ */
8
+ /**
9
+ * Keyed MiMC-5 permutation: `out = MiMC(input, key)`.
10
+ *
11
+ * Used as a PRF with the SECRET in `key` and a small public domain tag in
12
+ * `input` (note keystream: key = ECDH shared x, input = 1 / 2; view-key
13
+ * derivation: key = spend secret, input = 7). Keep that orientation: with
14
+ * key = 0 the function degenerates to a keyless public permutation.
15
+ * @param input any integer, reduced mod r
16
+ * @param key any integer, reduced mod r
17
+ * @returns a field element in [0, r)
3
18
  */
4
19
  export declare function mimc5perm(input: bigint, key: bigint): bigint;
package/dist/mimc.js CHANGED
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  /**
3
- * mimc.ts MiMC-5 permutation over the BLS12-381 scalar field.
3
+ * mimc.ts - MiMC-5 permutation over the BLS12-381 scalar field.
4
+ *
5
+ * Bit-for-bit the same function as MiMC5Perm in circuits/lib/mimc.circom:
6
+ * 110 rounds of x -> (x + key + c_i)^5, output = final state + key. The round
7
+ * constants come from the generated constants table shared with the circuit.
4
8
  */
5
9
  Object.defineProperty(exports, "__esModule", { value: true });
6
10
  exports.mimc5perm = mimc5perm;
@@ -8,6 +12,17 @@ const constants_1 = require("./constants");
8
12
  const jubjub_1 = require("./jubjub");
9
13
  const ROUNDS = constants_1.CONSTANTS.MIMC_ROUNDS;
10
14
  const RC = constants_1.CONSTANTS.MIMC_C.map((c) => BigInt(c));
15
+ /**
16
+ * Keyed MiMC-5 permutation: `out = MiMC(input, key)`.
17
+ *
18
+ * Used as a PRF with the SECRET in `key` and a small public domain tag in
19
+ * `input` (note keystream: key = ECDH shared x, input = 1 / 2; view-key
20
+ * derivation: key = spend secret, input = 7). Keep that orientation: with
21
+ * key = 0 the function degenerates to a keyless public permutation.
22
+ * @param input any integer, reduced mod r
23
+ * @param key any integer, reduced mod r
24
+ * @returns a field element in [0, r)
25
+ */
11
26
  function mimc5perm(input, key) {
12
27
  let st = (0, jubjub_1.mod)(input);
13
28
  key = (0, jubjub_1.mod)(key);
package/dist/note.d.ts CHANGED
@@ -1,31 +1,98 @@
1
1
  /**
2
- * note.ts ECDH encrypted note + view/spend key derivation.
2
+ * note.ts - ECDH encrypted note + view/spend key derivation.
3
3
  *
4
4
  * Notes are encrypted to the receiver's VIEWING key (`pk_view`). Holding
5
5
  * `sk_view` lets the receiver (or a granted auditor) read amounts but not spend.
6
+ *
7
+ * Scheme (mirrors circuits/lib/encrypted_note.circom, which proves the sender
8
+ * formed the note this way):
9
+ * R = k*G (ephemeral key, published)
10
+ * shared = k*pk_view == sk_view*R (ECDH)
11
+ * mask_i = MiMC(DOMAIN_i, key = shared.x)
12
+ * enc_amount = amount + mask_1, enc_blind = amount_blind + mask_2 (mod r)
13
+ * The masks are a one-time pad keyed by shared.x: k MUST be fresh per note.
14
+ * Reusing k toward the same receiver reuses the pad and leaks the difference
15
+ * of the two plaintexts. The circuit cannot enforce freshness across proofs.
6
16
  */
7
17
  import { Point } from "./jubjub";
18
+ /**
19
+ * The on-chain note payload - everything here is PUBLIC (it travels in the
20
+ * InternalTransfer message). The ephemeral scalar k is deliberately not part of
21
+ * it; `MetonSDK.transfer()` returns it separately as `ephScalar`.
22
+ */
8
23
  export interface Note {
24
+ /** x of the ephemeral key R = k*G */
9
25
  Rx: bigint;
26
+ /** y of the ephemeral key R = k*G */
10
27
  Ry: bigint;
28
+ /** amount + MiMC(1, shared.x) mod r */
11
29
  enc_amount: bigint;
30
+ /** amount_blind + MiMC(2, shared.x) mod r */
12
31
  enc_blind: bigint;
13
- eph_scalar: bigint;
14
- shared_x: bigint;
15
32
  }
16
- /** Derive a viewing keypair one-way from the spend secret. */
33
+ /**
34
+ * Derive a viewing keypair one-way from the spend secret:
35
+ * sk_view = MiMC(DOMAIN_VIEW = 7, key = spendSk) mod L, pk_view = sk_view*G.
36
+ *
37
+ * One-way, so `skView` can be handed to an auditor without exposing `spendSk`.
38
+ * Deterministic: the same spend secret always yields the same view key, which is
39
+ * what makes the (immutable) on-chain `Register` recoverable from the seed.
40
+ * @param spendSk root secret (any integer; reduced mod r before hashing)
41
+ * @returns skView in [1, L) (0 is mapped to 1 so pkView is never the identity)
42
+ * and pkView, always a prime-order-subgroup point
43
+ */
17
44
  export declare function deriveViewKey(spendSk: bigint): {
18
45
  skView: bigint;
19
46
  pkView: Point;
20
47
  };
21
- /** Encrypt (amount, amount_blind) to a receiver viewing key with ephemeral k. */
48
+ /**
49
+ * Encrypt (amount, amount_blind) to a receiver viewing key with ephemeral k.
50
+ *
51
+ * Performs NO validation: the receiver key is not checked for subgroup
52
+ * membership and k is not checked against 0 mod L. Use `MetonSDK.transfer()`
53
+ * or `sealNote()` for the validated path; the transfer circuit rejects a
54
+ * low-order key or an identity R anyway, but only after witness generation.
55
+ * @param amount token amount (64-bit in the circuit)
56
+ * @param amountBlind blinding of the amount commitment (252-bit scalar)
57
+ * @param receiverViewPkX,receiverViewPkY the recipient's pk_view (subgroup point)
58
+ * @param ephScalar k - MUST be fresh, uniformly random per note (see file header)
59
+ * @returns the public note; k itself is a secret the caller may keep or discard
60
+ */
22
61
  export declare function encryptNote(amount: bigint, amountBlind: bigint, receiverViewPkX: bigint, receiverViewPkY: bigint, ephScalar: bigint): Note;
23
- /** Decrypt a note with a viewing secret (sk_view). */
62
+ /**
63
+ * Decrypt a note with a viewing secret (sk_view).
64
+ *
65
+ * R is validated first: it must be a non-identity point of the prime-order
66
+ * subgroup. An honest note always is (the circuit proves R = k*G, k != 0 mod L);
67
+ * anything else is malformed, and multiplying sk_view into a low-order point
68
+ * would leak bits of the viewing key.
69
+ *
70
+ * Decryption cannot fail otherwise: a wrong key or a note addressed to someone
71
+ * else yields garbage, not an error. Use `viewNote` to check the result against
72
+ * the on-chain amount commitment.
73
+ * @param skView the recipient's viewing secret (or an auditor's copy of it)
74
+ * @param Rx,Ry the note's ephemeral key, canonical coordinates in [0, r)
75
+ * @param encAmount,encBlind the masked values from the note
76
+ * @returns amount and amountBlind as field elements; an honest note gives
77
+ * amount < 2^64 and amountBlind < 2^252
78
+ * @throws Error if R is out of range, the identity, off-curve or not in the
79
+ * prime-order subgroup (malformed note; a proven note never is)
80
+ */
24
81
  export declare function decryptNote(skView: bigint, Rx: bigint, Ry: bigint, encAmount: bigint, encBlind: bigint): {
25
82
  amount: bigint;
26
83
  amountBlind: bigint;
27
84
  };
28
- /** Decrypt AND verify the note opens the on-chain amount_commit point. */
85
+ /**
86
+ * Decrypt AND verify the note opens the on-chain amount_commit point.
87
+ *
88
+ * `valid` is true iff commit(amount, amountBlind) equals (amountCommitX,
89
+ * amountCommitY) exactly (canonical coordinates). Since the transfer proof binds
90
+ * the encrypted values to that commitment, `valid == false` means the note was
91
+ * decrypted with the wrong viewing key or paired with the wrong commitment,
92
+ * not that the sender cheated. Callers that credit a balance from a note MUST
93
+ * check `valid`.
94
+ * @throws Error from decryptNote on a malformed R
95
+ */
29
96
  export declare function viewNote(skView: bigint, note: {
30
97
  Rx: bigint;
31
98
  Ry: bigint;