@arcium-hq/client 0.9.2 → 0.9.3

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 (92) hide show
  1. package/build/index.cjs +15 -3
  2. package/build/index.mjs +15 -4
  3. package/build/types/arcis/arcisModule.d.ts +26 -0
  4. package/build/types/arcis/arcisModule.d.ts.map +1 -0
  5. package/build/types/arcis/arcisType.d.ts +76 -0
  6. package/build/types/arcis/arcisType.d.ts.map +1 -0
  7. package/build/types/arcis/packer.d.ts +63 -0
  8. package/build/types/arcis/packer.d.ts.map +1 -0
  9. package/build/types/arcis/packing.d.ts +33 -0
  10. package/build/types/arcis/packing.d.ts.map +1 -0
  11. package/build/types/callback.d.ts +21 -0
  12. package/build/types/callback.d.ts.map +1 -0
  13. package/build/types/constants.d.ts +101 -0
  14. package/build/types/constants.d.ts.map +1 -0
  15. package/build/types/cryptography/aes128Cipher.d.ts +14 -0
  16. package/build/types/cryptography/aes128Cipher.d.ts.map +1 -0
  17. package/build/types/cryptography/aes192Cipher.d.ts +14 -0
  18. package/build/types/cryptography/aes192Cipher.d.ts.map +1 -0
  19. package/build/types/cryptography/aes256Cipher.d.ts +14 -0
  20. package/build/types/cryptography/aes256Cipher.d.ts.map +1 -0
  21. package/build/types/cryptography/aesCtrCipher.d.ts +36 -0
  22. package/build/types/cryptography/aesCtrCipher.d.ts.map +1 -0
  23. package/build/types/cryptography/arcisEd25519.d.ts +8 -0
  24. package/build/types/cryptography/arcisEd25519.d.ts.map +1 -0
  25. package/build/types/cryptography/cSplRescueCipher.d.ts +29 -0
  26. package/build/types/cryptography/cSplRescueCipher.d.ts.map +1 -0
  27. package/build/types/cryptography/cryptography.d.ts +38 -0
  28. package/build/types/cryptography/cryptography.d.ts.map +1 -0
  29. package/build/types/cryptography/hkdf.d.ts +37 -0
  30. package/build/types/cryptography/hkdf.d.ts.map +1 -0
  31. package/build/types/cryptography/hmac.d.ts +22 -0
  32. package/build/types/cryptography/hmac.d.ts.map +1 -0
  33. package/build/types/cryptography/rescueCipher.d.ts +29 -0
  34. package/build/types/cryptography/rescueCipher.d.ts.map +1 -0
  35. package/build/types/cryptography/rescueCipherCommon.d.ts +45 -0
  36. package/build/types/cryptography/rescueCipherCommon.d.ts.map +1 -0
  37. package/build/types/cryptography/rescueDesc.d.ts +80 -0
  38. package/build/types/cryptography/rescueDesc.d.ts.map +1 -0
  39. package/build/types/cryptography/rescuePrimeHash.d.ts +23 -0
  40. package/build/types/cryptography/rescuePrimeHash.d.ts.map +1 -0
  41. package/build/types/ctUtils.d.ts +50 -0
  42. package/build/types/ctUtils.d.ts.map +1 -0
  43. package/build/{index.d.ts → types/idl/arcium.d.ts} +5 -901
  44. package/build/types/idl/arcium.d.ts.map +1 -0
  45. package/build/types/idl/arcium_staking.d.ts +4589 -0
  46. package/build/types/idl/arcium_staking.d.ts.map +1 -0
  47. package/build/types/idl/index.d.ts +15 -0
  48. package/build/types/idl/index.d.ts.map +1 -0
  49. package/build/types/index.d.ts +33 -0
  50. package/build/types/index.d.ts.map +1 -0
  51. package/build/types/localEnv.d.ts +15 -0
  52. package/build/types/localEnv.d.ts.map +1 -0
  53. package/build/types/matrix.d.ts +39 -0
  54. package/build/types/matrix.d.ts.map +1 -0
  55. package/build/types/onchain.d.ts +223 -0
  56. package/build/types/onchain.d.ts.map +1 -0
  57. package/build/types/pda.d.ts +89 -0
  58. package/build/types/pda.d.ts.map +1 -0
  59. package/build/types/utils.d.ts +65 -0
  60. package/build/types/utils.d.ts.map +1 -0
  61. package/package.json +6 -6
  62. package/src/arcis/arcisModule.ts +39 -0
  63. package/src/arcis/arcisType.ts +303 -0
  64. package/src/arcis/packer.ts +152 -0
  65. package/src/arcis/packing.ts +115 -0
  66. package/src/callback.ts +101 -0
  67. package/src/constants.ts +104 -0
  68. package/src/cryptography/aes128Cipher.ts +16 -0
  69. package/src/cryptography/aes192Cipher.ts +16 -0
  70. package/src/cryptography/aes256Cipher.ts +16 -0
  71. package/src/cryptography/aesCtrCipher.ts +84 -0
  72. package/src/cryptography/arcisEd25519.ts +96 -0
  73. package/src/cryptography/cSplRescueCipher.ts +41 -0
  74. package/src/cryptography/cryptography.ts +82 -0
  75. package/src/cryptography/hkdf.ts +58 -0
  76. package/src/cryptography/hmac.ts +66 -0
  77. package/src/cryptography/rescueCipher.ts +41 -0
  78. package/src/cryptography/rescueCipherCommon.ts +211 -0
  79. package/src/cryptography/rescueDesc.ts +492 -0
  80. package/src/cryptography/rescuePrimeHash.ts +72 -0
  81. package/src/ctUtils.ts +124 -0
  82. package/src/idl/arcium.json +12281 -0
  83. package/src/idl/arcium.ts +12287 -0
  84. package/src/idl/arcium_staking.json +4582 -0
  85. package/src/idl/arcium_staking.ts +4588 -0
  86. package/src/idl/index.ts +20 -0
  87. package/src/index.ts +32 -0
  88. package/src/localEnv.ts +39 -0
  89. package/src/matrix.ts +215 -0
  90. package/src/onchain.ts +1020 -0
  91. package/src/pda.ts +203 -0
  92. package/src/utils.ts +126 -0
@@ -0,0 +1,65 @@
1
+ import * as anchor from '@coral-xyz/anchor';
2
+ import { ArciumIdlType } from './idl/index.js';
3
+ /**
4
+ * Check if code is running in a browser environment.
5
+ * @returns true if window object exists, false otherwise.
6
+ */
7
+ export declare function isBrowser(): boolean;
8
+ /**
9
+ * Conditionally logs a message if logging is enabled.
10
+ * @param log - Whether to output the log.
11
+ * @param args - Arguments to pass to console.log.
12
+ */
13
+ export declare function optionalLog(log: boolean, ...args: Parameters<typeof console.log>): void;
14
+ /**
15
+ * Calculate the minimum number of bits needed to represent a value.
16
+ * Formula: floor(log2(max)) + 1 for unsigned, +1 for signed, +1 for diff of two negatives.
17
+ * @param max - Bigint value to measure.
18
+ * @returns Number of bits required.
19
+ */
20
+ export declare function getBinSize(max: bigint): bigint;
21
+ /**
22
+ * Number of mantissa bits for double-precision floating point values.
23
+ */
24
+ export declare const DOUBLE_PRECISION_MANTISSA = 52;
25
+ /**
26
+ * Encode a value as a bigint suitable for Rescue encryption, handling booleans, bigints, and numbers.
27
+ * The encoding is performed in constant-time to avoid leaking information through timing side-channels.
28
+ * Throws if the value is out of the supported range for the field.
29
+ * @param v - Value to encode (bigint, number, or boolean).
30
+ * @returns Encoded value as a bigint.
31
+ */
32
+ export declare function encodeAsRescueEncryptable(v: bigint | number | boolean): bigint;
33
+ /**
34
+ * Decode a Rescue-decrypted value back to a signed bigint.
35
+ * Handle the conversion from field element representation to signed integer.
36
+ * @param v - Decrypted field element value.
37
+ * @returns Decoded signed bigint value.
38
+ */
39
+ export declare function decodeRescueDecryptedToBigInt(v: bigint): bigint;
40
+ /**
41
+ * Decode a Rescue-decrypted value back to a JavaScript number.
42
+ * Convert from field element representation to a floating-point number.
43
+ * @param v - Decrypted field element value.
44
+ * @returns Decoded number value.
45
+ */
46
+ export declare function decodeRescueDecryptedToNumber(v: bigint): number;
47
+ /**
48
+ * Generate a random integer in the range [min, max] (inclusive).
49
+ * @param min - Minimum value (inclusive).
50
+ * @param max - Maximum value (inclusive).
51
+ * @returns Random integer between min and max.
52
+ */
53
+ export declare function randomInt(min: number, max: number): number;
54
+ /**
55
+ * Reference to a computation in a mempool or executing pool.
56
+ * Contains the computation offset and priority fee information.
57
+ */
58
+ export type ComputationReference = anchor.IdlTypes<ArciumIdlType>['computationReference'];
59
+ /**
60
+ * Check if a computation reference is null (all zeros).
61
+ * @param ref - Computation reference to check.
62
+ * @returns true if the reference is null, false otherwise.
63
+ */
64
+ export declare function isNullRef(ref: ComputationReference): boolean;
65
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAG5C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C;;;GAGG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAKnC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,GAAG,CAAC,QAKhF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED;;GAEG;AACH,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAoB9E;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,sBAAsB,CAAC,CAAC;AAE1F;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAM5D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcium-hq/client",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "description": "Client SDK for interacting with encrypted Solana programs",
5
5
  "author": "Arcium",
6
6
  "license": "GPL-3.0-only",
@@ -14,20 +14,21 @@
14
14
  "compile": "yarn bundle:cjs && yarn bundle:esm && yarn bundle:dts",
15
15
  "bundle:cjs": "node ../../node_modules/rollup/dist/bin/rollup --config config/rollup.config.cjs.js",
16
16
  "bundle:esm": "node ../../node_modules/rollup/dist/bin/rollup --config config/rollup.config.esm.js",
17
- "bundle:dts": "node ../../node_modules/rollup/dist/bin/rollup --config config/rollup.config.dts.js",
17
+ "bundle:dts": "tsc --emitDeclarationOnly",
18
18
  "test-single": "mocha $1 --exit"
19
19
  },
20
20
  "type": "module",
21
21
  "module": "build/index.mjs",
22
- "types": "build/index.d.ts",
22
+ "types": "build/types/index.d.ts",
23
23
  "files": [
24
24
  "build/index.cjs",
25
25
  "build/index.mjs",
26
- "build/index.d.ts"
26
+ "build/types/",
27
+ "src/"
27
28
  ],
28
29
  "exports": [
29
30
  {
30
- "types": "./build/index.d.ts",
31
+ "types": "./build/types/index.d.ts",
31
32
  "import": "./build/index.mjs",
32
33
  "require": "./build/index.cjs"
33
34
  }
@@ -52,7 +53,6 @@
52
53
  "eslint-plugin-import": "^2.31.0",
53
54
  "mocha": "^10.8.2",
54
55
  "rollup": "^4.24.0",
55
- "rollup-plugin-dts": "^6.1.1",
56
56
  "ts-node": "^10.9.2",
57
57
  "typescript": "^5.6.3",
58
58
  "typescript-eslint": "^8.15.0"
@@ -0,0 +1,39 @@
1
+ import {ArcisType} from "./arcisType.js";
2
+ import fs from "fs";
3
+
4
+ /**
5
+ * Container for circuit type definitions.
6
+ * Loaded from generated JSON files by build tools -- not typically used directly.
7
+ * @internal
8
+ */
9
+ export class ArcisModule {
10
+ /** Map of type name to parsed ArcisType. */
11
+ types: {[typeName: string]: ArcisType};
12
+
13
+ constructor(types: {[typeName: string]: ArcisType}) {
14
+ this.types = types;
15
+ }
16
+
17
+ /**
18
+ * Parse module from JSON object (as produced by the Arcium compiler).
19
+ * @param json - Raw JSON object with type name keys.
20
+ */
21
+ public static fromJson(json: unknown): ArcisModule {
22
+ const typedJson = json as { [typeName: string]: unknown };
23
+ const res: {[typeName: string]: ArcisType} = {};
24
+ for (const key in typedJson) {
25
+ res[key] = ArcisType.fromJson(key, typedJson[key]);
26
+ }
27
+ return new ArcisModule(res);
28
+ }
29
+
30
+ /**
31
+ * Load module from a JSON file on disk.
32
+ * @param path - Absolute or relative path to the JSON file.
33
+ */
34
+ public static loadFromFile(path: string): ArcisModule {
35
+ const file = fs.readFileSync(path);
36
+ const json = JSON.parse(file.toString());
37
+ return ArcisModule.fromJson(json);
38
+ }
39
+ }
@@ -0,0 +1,303 @@
1
+ import {arcisPacking, DataSize} from "./packing.js";
2
+ import {encodeAsRescueEncryptable, decodeRescueDecryptedToNumber} from "../utils.js";
3
+
4
+ /** Length of a Pubkey in bytes (Solana public keys are 32 bytes) */
5
+ const PUBKEY_BYTE_LENGTH = 32;
6
+
7
+ /** Maximum value a Pubkey bigint can hold (2^256) */
8
+ const PUBKEY_MAX_VALUE = 1n << 256n;
9
+
10
+ /**
11
+ * Field definition for circuit input/output packing.
12
+ * Pass an array of these to {@link createPacker} to create a type-safe packer.
13
+ *
14
+ * @example
15
+ * import { FieldInfo } from '@arcium-hq/client';
16
+ *
17
+ * const fields: FieldInfo[] = [
18
+ * { name: 'amount', type: { Integer: { signed: false, width: 64 } } },
19
+ * { name: 'active', type: 'Bool' },
20
+ * ];
21
+ */
22
+ export interface FieldInfo {
23
+ /** Field name matching the circuit parameter. For array types, use indexed notation (`bytes[0]`, `bytes[1]`) - the packer groups these back into arrays on unpack. */
24
+ name: string;
25
+ /** Field type: Integer with sign/width, Bool, FullInteger (256-bit), Float, or Pubkey. */
26
+ type: { Integer: { signed: boolean; width: number } } | 'Bool' | 'FullInteger' | 'Float' | 'Pubkey';
27
+ }
28
+
29
+ enum ArcisValueKind {
30
+ Integer,
31
+ FullInteger,
32
+ Bool,
33
+ Float,
34
+ Pubkey,
35
+ }
36
+
37
+ /**
38
+ * Integer type metadata. Used internally by packer.
39
+ * @internal
40
+ */
41
+ export class IntegerInfo {
42
+ signed: boolean;
43
+ width: bigint;
44
+ constructor(signed:boolean, width: bigint) {
45
+ this.signed = signed;
46
+ this.width = width;
47
+ }
48
+ public static fromJson(json: { "signed": boolean, "width": number}): IntegerInfo {
49
+ return new IntegerInfo(json.signed, BigInt(json.width));
50
+ }
51
+
52
+ minValue(): bigint {
53
+ return this.signed ? -1n << (this.width - 1n) : 0n;
54
+ }
55
+ isWithinBounds(b: bigint): boolean {
56
+ const minVal = this.minValue();
57
+ const maxVal = this.signed ? 1n << (this.width - 1n) : 1n << this.width;
58
+ return minVal <= b && b < maxVal;
59
+ }
60
+ name(): string {
61
+ return (this.signed ? "i" : "u") + this.width;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Runtime field representation. Used internally by packer.
67
+ * @internal
68
+ */
69
+ export class ArcisValueField {
70
+ name: string;
71
+ kind: ArcisValueKind;
72
+ info: IntegerInfo | undefined;
73
+ constructor(name: string, kind: ArcisValueKind, info?: IntegerInfo) {
74
+ this.name = name;
75
+ this.kind = kind;
76
+ this.info = info;
77
+ }
78
+ public static fromJson(json: unknown): ArcisValueField {
79
+ const typedJson = json as { "name": string, "type": unknown};
80
+ const name = typedJson["name"];
81
+ const rawType = typedJson["type"] as string | {"Integer": { "signed": boolean, "width": number}};
82
+ let kind = ArcisValueKind.Integer;
83
+ switch (rawType) {
84
+ case "FullInteger":
85
+ kind = ArcisValueKind.FullInteger;
86
+ break;
87
+ case "Bool":
88
+ kind = ArcisValueKind.Bool;
89
+ break;
90
+ case "Float":
91
+ kind = ArcisValueKind.Float;
92
+ break;
93
+ case "Pubkey":
94
+ kind = ArcisValueKind.Pubkey;
95
+ break;
96
+ }
97
+ if (kind === ArcisValueKind.Integer) {
98
+ if (typeof rawType === "string") {
99
+ throw new TypeError("Expected Integer type object with signed/width properties");
100
+ }
101
+ const info = IntegerInfo.fromJson(rawType.Integer);
102
+ return new ArcisValueField(name, kind, info);
103
+ }
104
+ else {
105
+ return new ArcisValueField(name, kind);
106
+ }
107
+ }
108
+
109
+ toBigInt(arg: unknown): bigint {
110
+ if (this.kind === ArcisValueKind.Float) {
111
+ if (typeof arg !== 'number') {
112
+ throw new TypeError(`Field ${this.name} expected a number, got ${typeof arg}`);
113
+ }
114
+ return encodeAsRescueEncryptable(arg);
115
+ }
116
+
117
+ let value = arg;
118
+ if (typeof value === 'number') {
119
+ if (!Number.isInteger(value)) {
120
+ throw new TypeError(`Field ${this.name}: expected integer, got float ${value}`);
121
+ }
122
+ if (!Number.isSafeInteger(value)) {
123
+ throw new RangeError(`Field ${this.name}: ${value} exceeds safe integer range, use BigInt`);
124
+ }
125
+ value = BigInt(value);
126
+ }
127
+
128
+ switch (this.kind) {
129
+ case ArcisValueKind.Bool:
130
+ if (typeof value !== 'boolean') {
131
+ throw new TypeError(`Field ${this.name} expected a boolean, got ${typeof value}`);
132
+ }
133
+ return BigInt(value);
134
+ case ArcisValueKind.FullInteger:
135
+ if (typeof value !== 'bigint') {
136
+ throw new TypeError(`Field ${this.name} expected a bigint, got ${typeof value}`);
137
+ }
138
+ return value;
139
+ case ArcisValueKind.Integer:
140
+ if (typeof value !== 'bigint') {
141
+ throw new TypeError(`Field ${this.name} expected a bigint, got ${typeof value}`);
142
+ }
143
+ if (this.info === undefined) {
144
+ throw new TypeError(`Field ${this.name} is an integer, but signedness and width are unknown`);
145
+ }
146
+ if (!this.info.isWithinBounds(value)) {
147
+ const min = this.info.minValue();
148
+ const max = this.info.signed ? (1n << (this.info.width - 1n)) - 1n : (1n << this.info.width) - 1n;
149
+ throw new RangeError(`Field "${this.name}" (${this.info.name()}): value ${value} out of range [${min}, ${max}]`);
150
+ }
151
+ return value - this.info.minValue();
152
+ case ArcisValueKind.Pubkey: {
153
+ if (!(value instanceof Uint8Array)) {
154
+ throw new TypeError(`Field ${this.name} expected a Uint8Array, got ${typeof value}`);
155
+ }
156
+ if (value.length !== PUBKEY_BYTE_LENGTH) {
157
+ throw new RangeError(`Field ${this.name} expected ${PUBKEY_BYTE_LENGTH}-byte Uint8Array, got ${value.length} bytes`);
158
+ }
159
+ let pubkeyResult = 0n;
160
+ for (let i = 0; i < PUBKEY_BYTE_LENGTH; i++) {
161
+ pubkeyResult |= BigInt(value[i]) << BigInt(i * 8);
162
+ }
163
+ return pubkeyResult;
164
+ }
165
+ default: {
166
+ const _exhaustive: never = this.kind;
167
+ throw new Error(`Unhandled ArcisValueKind: ${_exhaustive}`);
168
+ }
169
+ }
170
+ }
171
+ fromBigInt(arg: bigint): boolean | number | bigint | Uint8Array {
172
+ switch (this.kind) {
173
+ case ArcisValueKind.Bool:
174
+ switch (arg) {
175
+ case 0n:
176
+ return false;
177
+ case 1n:
178
+ return true;
179
+ default:
180
+ throw new RangeError(`Field ${this.name}: Bool must be 0 or 1, got ${arg}`);
181
+ }
182
+ case ArcisValueKind.Float:
183
+ return decodeRescueDecryptedToNumber(arg);
184
+ case ArcisValueKind.FullInteger:
185
+ return arg;
186
+ case ArcisValueKind.Integer:
187
+ if (this.info === undefined) {
188
+ throw new TypeError("Integer type without integer info");
189
+ }
190
+ return arg + this.info.minValue();
191
+ case ArcisValueKind.Pubkey: {
192
+ if (arg < 0n) {
193
+ throw new RangeError(`Field ${this.name}: Pubkey cannot be negative`);
194
+ }
195
+ if (arg >= PUBKEY_MAX_VALUE) {
196
+ throw new RangeError(`Field ${this.name}: Pubkey exceeds 256 bits`);
197
+ }
198
+ const pubkeyBytes = new Uint8Array(PUBKEY_BYTE_LENGTH);
199
+ let remaining = arg;
200
+ for (let i = 0; i < PUBKEY_BYTE_LENGTH; i++) {
201
+ pubkeyBytes[i] = Number(remaining & 0xFFn);
202
+ remaining >>= 8n;
203
+ }
204
+ return pubkeyBytes;
205
+ }
206
+ default: {
207
+ const _exhaustive: never = this.kind;
208
+ throw new Error(`Unhandled ArcisValueKind: ${_exhaustive}`);
209
+ }
210
+ }
211
+ }
212
+
213
+ public static fromFieldInfo(info: FieldInfo): ArcisValueField {
214
+ const name = info.name;
215
+ if (info.type === 'Bool') {
216
+ return new ArcisValueField(name, ArcisValueKind.Bool);
217
+ }
218
+ if (info.type === 'FullInteger') {
219
+ return new ArcisValueField(name, ArcisValueKind.FullInteger);
220
+ }
221
+ if (info.type === 'Float') {
222
+ return new ArcisValueField(name, ArcisValueKind.Float);
223
+ }
224
+ if (info.type === 'Pubkey') {
225
+ return new ArcisValueField(name, ArcisValueKind.Pubkey);
226
+ }
227
+ if (typeof info.type === 'object' && 'Integer' in info.type) {
228
+ const intInfo = new IntegerInfo(
229
+ info.type.Integer.signed,
230
+ BigInt(info.type.Integer.width)
231
+ );
232
+ return new ArcisValueField(name, ArcisValueKind.Integer, intInfo);
233
+ }
234
+ throw new TypeError(`Unknown field type for ${name}`);
235
+ }
236
+
237
+ toDataSize(index: number): DataSize {
238
+ switch (this.kind) {
239
+ case ArcisValueKind.Pubkey:
240
+ return new DataSize(index);
241
+ case ArcisValueKind.Integer:
242
+ return new DataSize(index, Number(this.info?.width));
243
+ case ArcisValueKind.FullInteger:
244
+ return new DataSize(index);
245
+ case ArcisValueKind.Bool:
246
+ return new DataSize(index, 1);
247
+ case ArcisValueKind.Float:
248
+ return new DataSize(index);
249
+ }
250
+ }
251
+
252
+ }
253
+
254
+ /**
255
+ * Type container for pack/unpack operations. Used internally by packer.
256
+ * @internal
257
+ */
258
+ export class ArcisType {
259
+ name: string;
260
+ fields: ArcisValueField[];
261
+ constructor(name: string, fields: ArcisValueField[]) {
262
+ this.name = name;
263
+ this.fields = fields;
264
+ }
265
+
266
+ pack(rawData: unknown[]): bigint[] {
267
+ if (rawData.length !== this.fields.length) {
268
+ throw new RangeError(`Expected ${this.fields.length} fields, got ${rawData.length}`);
269
+ }
270
+ const data = rawData.map((val, index) => this.fields[index].toBigInt(val));
271
+ const dataSizes = this.fields.map((field, index) => field.toDataSize(index));
272
+ const packing = arcisPacking(dataSizes);
273
+ const plaintext: bigint[] = Array(packing[0]);
274
+ plaintext.fill(0n);
275
+ for (let idx = 0; idx < data.length; idx++) {
276
+ const packLocation = packing[1][idx];
277
+ plaintext[packLocation.index] += data[idx] << BigInt(packLocation.offset);
278
+ }
279
+ return plaintext
280
+ }
281
+ unpack(packed: bigint[]): (boolean | number | bigint | Uint8Array)[] {
282
+ const dataSizes = this.fields.map((field, index) => field.toDataSize(index));
283
+ const packing = arcisPacking(dataSizes);
284
+ if (packed.length < packing[0]) {
285
+ throw new RangeError(`Expected at least ${packing[0]} packed elements, got ${packed.length}`);
286
+ }
287
+ const res: (boolean | number | bigint | Uint8Array)[] = [];
288
+ for (let idx = 0; idx < dataSizes.length; idx++) {
289
+ const packLocation = packing[1][idx];
290
+ const dataSize = dataSizes[idx];
291
+ const val = dataSize.isFull ? packed[packLocation.index] : (packed[packLocation.index] >> BigInt(packLocation.offset)) % (1n << BigInt(dataSize.size));
292
+ res.push(this.fields[idx].fromBigInt(val));
293
+ }
294
+ return res;
295
+ }
296
+
297
+ public static fromJson(name: string, json: unknown): ArcisType {
298
+ const typedJson = json as unknown[];
299
+ const fields = typedJson.map(ArcisValueField.fromJson);
300
+ return new ArcisType(name, fields);
301
+ }
302
+
303
+ }
@@ -0,0 +1,152 @@
1
+ import { ArcisType, ArcisValueField, FieldInfo } from "./arcisType.js";
2
+
3
+ /**
4
+ * Type-safe packer for converting between TypeScript objects and circuit-compatible bigint arrays.
5
+ *
6
+ * @template TInput - Input object type with field names as keys
7
+ * @template TOutput - Output object type (typically same structure as input)
8
+ */
9
+ export interface Packer<TInput, TOutput> {
10
+ /**
11
+ * Pack input values into bigint array for circuit execution.
12
+ * @param data - Object with field values matching the packer's field definitions.
13
+ * @returns Packed bigint array ready for encryption.
14
+ * @throws Error if a required field is missing.
15
+ */
16
+ pack(data: TInput): bigint[];
17
+
18
+ /**
19
+ * Unpack circuit output back to typed object.
20
+ * @param packed - Bigint array from decrypted circuit output.
21
+ * @returns Typed object with field values.
22
+ */
23
+ unpack(packed: bigint[]): TOutput;
24
+ }
25
+
26
+ /** Extracts base field name from array-indexed names: "bytes[0]" -> "bytes" */
27
+ type ExtractBaseName<T extends string> =
28
+ T extends `${infer Base}[${string}]` ? Base : T;
29
+
30
+ /** Extracts all base field names from a readonly FieldInfo array. */
31
+ type FieldBaseNames<T extends readonly FieldInfo[]> =
32
+ ExtractBaseName<T[number]['name']>;
33
+
34
+ /**
35
+ * Validates that field base names match TInput keys. Returns `never` on mismatch.
36
+ * Only validates when using `as const`; skipped if types widen to `string`.
37
+ */
38
+ type ValidateFieldNames<TInput, TFields extends readonly FieldInfo[]> =
39
+ string extends FieldBaseNames<TFields>
40
+ ? TFields
41
+ : FieldBaseNames<TFields> extends keyof TInput
42
+ ? TFields
43
+ : never;
44
+
45
+ function parseFieldName(name: string): { base: string; index: number | null } {
46
+ const match = name.match(/^(.+)\[(\d+)\]$/);
47
+ if (match) {
48
+ return { base: match[1], index: parseInt(match[2], 10) };
49
+ }
50
+ return { base: name, index: null };
51
+ }
52
+
53
+ function extractValue<T extends Record<string, unknown>>(
54
+ data: T,
55
+ fieldName: string
56
+ ): unknown {
57
+ const { base, index } = parseFieldName(fieldName);
58
+ const value = (data as Record<string, unknown>)[base];
59
+
60
+ if (index !== null) {
61
+ if (!Array.isArray(value)) {
62
+ throw new TypeError(`Field "${base}" should be an array`);
63
+ }
64
+ if (index >= value.length) {
65
+ throw new RangeError(`Field "${base}[${index}]" out of bounds (array length: ${value.length})`);
66
+ }
67
+ return value[index];
68
+ }
69
+
70
+ return value;
71
+ }
72
+
73
+ function groupUnpackedValues(
74
+ values: (boolean | number | bigint | Uint8Array)[],
75
+ fieldNames: string[]
76
+ ): Record<string, unknown> {
77
+ const result: Record<string, unknown> = {};
78
+ for (let i = 0; i < fieldNames.length; i++) {
79
+ const { base, index } = parseFieldName(fieldNames[i]);
80
+ const value = values[i];
81
+ if (index !== null) {
82
+ if (!result[base]) {
83
+ result[base] = [];
84
+ }
85
+ (result[base] as unknown[])[index] = value;
86
+ }
87
+ else {
88
+ result[base] = value;
89
+ }
90
+ }
91
+ return result;
92
+ }
93
+
94
+ /**
95
+ * Create a type-safe packer from field definitions.
96
+ *
97
+ * Use `as const` on the fields array to enable compile-time field name validation.
98
+ *
99
+ * @param fields - Array of {@link FieldInfo} objects defining each field's name and type.
100
+ * @param typeName - Optional name for debugging (default: 'Packer').
101
+ * @returns Packer instance with pack() and unpack() methods.
102
+ * @throws TypeError if field types don't match expected values during pack/unpack.
103
+ * @throws RangeError if array index is out of bounds.
104
+ *
105
+ * @example
106
+ * import { createPacker } from '@arcium-hq/client';
107
+ *
108
+ * // Define fields matching your circuit's input type
109
+ * const fields = [
110
+ * { name: 'a', type: { Integer: { signed: false, width: 32 } } },
111
+ * { name: 'b', type: { Integer: { signed: false, width: 32 } } },
112
+ * ] as const;
113
+ *
114
+ * // Create packer with explicit input/output types
115
+ * const packer = createPacker<{ a: number; b: number }, { a: bigint; b: bigint }>(fields);
116
+ *
117
+ * // Pack values for circuit input
118
+ * const packed = packer.pack({ a: 10, b: 20 });
119
+ *
120
+ * // Unpack circuit output (from decrypted computation result)
121
+ * const result = packer.unpack(decryptedOutput);
122
+ */
123
+ export function createPacker<
124
+ TInput extends Record<string, unknown>,
125
+ TOutput extends Record<string, unknown>,
126
+ const TFields extends readonly FieldInfo[] = readonly FieldInfo[]
127
+ >(
128
+ fields: TFields & ValidateFieldNames<TInput, TFields>,
129
+ typeName: string = 'Packer'
130
+ ): Packer<TInput, TOutput> {
131
+ const arcisFields = fields.map(f => ArcisValueField.fromFieldInfo(f));
132
+ const arcisType = new ArcisType(typeName, arcisFields);
133
+ const fieldNames = fields.map(f => f.name);
134
+
135
+ return {
136
+ pack(data: TInput): bigint[] {
137
+ const arr = fieldNames.map(name => {
138
+ const value = extractValue(data, name);
139
+ if (value === undefined) {
140
+ throw new Error(`Missing required field: "${name}"`);
141
+ }
142
+ return value;
143
+ });
144
+ return arcisType.pack(arr);
145
+ },
146
+
147
+ unpack(packed: bigint[]): TOutput {
148
+ const arr = arcisType.unpack(packed);
149
+ return groupUnpackedValues(arr, fieldNames) as TOutput;
150
+ }
151
+ };
152
+ }