@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.
- package/build/index.cjs +15 -3
- package/build/index.mjs +15 -4
- package/build/types/arcis/arcisModule.d.ts +26 -0
- package/build/types/arcis/arcisModule.d.ts.map +1 -0
- package/build/types/arcis/arcisType.d.ts +76 -0
- package/build/types/arcis/arcisType.d.ts.map +1 -0
- package/build/types/arcis/packer.d.ts +63 -0
- package/build/types/arcis/packer.d.ts.map +1 -0
- package/build/types/arcis/packing.d.ts +33 -0
- package/build/types/arcis/packing.d.ts.map +1 -0
- package/build/types/callback.d.ts +21 -0
- package/build/types/callback.d.ts.map +1 -0
- package/build/types/constants.d.ts +101 -0
- package/build/types/constants.d.ts.map +1 -0
- package/build/types/cryptography/aes128Cipher.d.ts +14 -0
- package/build/types/cryptography/aes128Cipher.d.ts.map +1 -0
- package/build/types/cryptography/aes192Cipher.d.ts +14 -0
- package/build/types/cryptography/aes192Cipher.d.ts.map +1 -0
- package/build/types/cryptography/aes256Cipher.d.ts +14 -0
- package/build/types/cryptography/aes256Cipher.d.ts.map +1 -0
- package/build/types/cryptography/aesCtrCipher.d.ts +36 -0
- package/build/types/cryptography/aesCtrCipher.d.ts.map +1 -0
- package/build/types/cryptography/arcisEd25519.d.ts +8 -0
- package/build/types/cryptography/arcisEd25519.d.ts.map +1 -0
- package/build/types/cryptography/cSplRescueCipher.d.ts +29 -0
- package/build/types/cryptography/cSplRescueCipher.d.ts.map +1 -0
- package/build/types/cryptography/cryptography.d.ts +38 -0
- package/build/types/cryptography/cryptography.d.ts.map +1 -0
- package/build/types/cryptography/hkdf.d.ts +37 -0
- package/build/types/cryptography/hkdf.d.ts.map +1 -0
- package/build/types/cryptography/hmac.d.ts +22 -0
- package/build/types/cryptography/hmac.d.ts.map +1 -0
- package/build/types/cryptography/rescueCipher.d.ts +29 -0
- package/build/types/cryptography/rescueCipher.d.ts.map +1 -0
- package/build/types/cryptography/rescueCipherCommon.d.ts +45 -0
- package/build/types/cryptography/rescueCipherCommon.d.ts.map +1 -0
- package/build/types/cryptography/rescueDesc.d.ts +80 -0
- package/build/types/cryptography/rescueDesc.d.ts.map +1 -0
- package/build/types/cryptography/rescuePrimeHash.d.ts +23 -0
- package/build/types/cryptography/rescuePrimeHash.d.ts.map +1 -0
- package/build/types/ctUtils.d.ts +50 -0
- package/build/types/ctUtils.d.ts.map +1 -0
- package/build/{index.d.ts → types/idl/arcium.d.ts} +5 -901
- package/build/types/idl/arcium.d.ts.map +1 -0
- package/build/types/idl/arcium_staking.d.ts +4589 -0
- package/build/types/idl/arcium_staking.d.ts.map +1 -0
- package/build/types/idl/index.d.ts +15 -0
- package/build/types/idl/index.d.ts.map +1 -0
- package/build/types/index.d.ts +33 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/localEnv.d.ts +15 -0
- package/build/types/localEnv.d.ts.map +1 -0
- package/build/types/matrix.d.ts +39 -0
- package/build/types/matrix.d.ts.map +1 -0
- package/build/types/onchain.d.ts +223 -0
- package/build/types/onchain.d.ts.map +1 -0
- package/build/types/pda.d.ts +89 -0
- package/build/types/pda.d.ts.map +1 -0
- package/build/types/utils.d.ts +65 -0
- package/build/types/utils.d.ts.map +1 -0
- package/package.json +6 -6
- package/src/arcis/arcisModule.ts +39 -0
- package/src/arcis/arcisType.ts +303 -0
- package/src/arcis/packer.ts +152 -0
- package/src/arcis/packing.ts +115 -0
- package/src/callback.ts +101 -0
- package/src/constants.ts +104 -0
- package/src/cryptography/aes128Cipher.ts +16 -0
- package/src/cryptography/aes192Cipher.ts +16 -0
- package/src/cryptography/aes256Cipher.ts +16 -0
- package/src/cryptography/aesCtrCipher.ts +84 -0
- package/src/cryptography/arcisEd25519.ts +96 -0
- package/src/cryptography/cSplRescueCipher.ts +41 -0
- package/src/cryptography/cryptography.ts +82 -0
- package/src/cryptography/hkdf.ts +58 -0
- package/src/cryptography/hmac.ts +66 -0
- package/src/cryptography/rescueCipher.ts +41 -0
- package/src/cryptography/rescueCipherCommon.ts +211 -0
- package/src/cryptography/rescueDesc.ts +492 -0
- package/src/cryptography/rescuePrimeHash.ts +72 -0
- package/src/ctUtils.ts +124 -0
- package/src/idl/arcium.json +12281 -0
- package/src/idl/arcium.ts +12287 -0
- package/src/idl/arcium_staking.json +4582 -0
- package/src/idl/arcium_staking.ts +4588 -0
- package/src/idl/index.ts +20 -0
- package/src/index.ts +32 -0
- package/src/localEnv.ts +39 -0
- package/src/matrix.ts +215 -0
- package/src/onchain.ts +1020 -0
- package/src/pda.ts +203 -0
- package/src/utils.ts +126 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Size descriptor for a single field in circuit packing.
|
|
3
|
+
* A "full" field occupies an entire slot; partial fields are bin-packed together.
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export class DataSize {
|
|
7
|
+
/** Whether this field occupies a full slot. */
|
|
8
|
+
isFull: boolean;
|
|
9
|
+
/** Bit width of the field (0 if full). */
|
|
10
|
+
size: number;
|
|
11
|
+
/** Original index in the fields array. */
|
|
12
|
+
index: number;
|
|
13
|
+
|
|
14
|
+
constructor(index: number, size?: number) {
|
|
15
|
+
const isUndefined = size === undefined;
|
|
16
|
+
this.isFull = isUndefined;
|
|
17
|
+
this.size = isUndefined ? 0 : size;
|
|
18
|
+
this.index = index;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
function sortDataSizes(arr: DataSize[]): DataSize[] {
|
|
24
|
+
return [...arr].sort((left, right) => {
|
|
25
|
+
if (left.isFull !== right.isFull) {
|
|
26
|
+
return left.isFull ? -1 : +1;
|
|
27
|
+
}
|
|
28
|
+
if (left.size !== right.size) {
|
|
29
|
+
return right.size - left.size;
|
|
30
|
+
}
|
|
31
|
+
return left.index - right.index;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Packed location of a field within the slot array.
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export class PackLocation {
|
|
40
|
+
/** Slot index in the packed array. */
|
|
41
|
+
index: number;
|
|
42
|
+
/** Bit offset within the slot. */
|
|
43
|
+
offset: number;
|
|
44
|
+
|
|
45
|
+
constructor(index: number, offset: number) {
|
|
46
|
+
this.index = index;
|
|
47
|
+
this.offset = offset;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class PackingState {
|
|
52
|
+
maxSize: number;
|
|
53
|
+
loads: number[] = [];
|
|
54
|
+
nFull: number = 0;
|
|
55
|
+
lastInsert: number = 0;
|
|
56
|
+
constructor(maxSize: number) {
|
|
57
|
+
this.maxSize = maxSize;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getCurrentLoad(index: number): number {
|
|
61
|
+
return this.loads[index] === undefined ? 0 : this.loads[index];
|
|
62
|
+
}
|
|
63
|
+
canFit(index: number, size: number): boolean {
|
|
64
|
+
return this.getCurrentLoad(index) + size <= this.maxSize;
|
|
65
|
+
}
|
|
66
|
+
chooseInsertLocation(size: number): number {
|
|
67
|
+
if (this.canFit(this.nFull, size)) {
|
|
68
|
+
return this.nFull;
|
|
69
|
+
}
|
|
70
|
+
if (this.canFit(this.lastInsert, size)) {
|
|
71
|
+
return this.lastInsert;
|
|
72
|
+
}
|
|
73
|
+
return this.lastInsert + 1;
|
|
74
|
+
}
|
|
75
|
+
insert(size: DataSize): PackLocation {
|
|
76
|
+
if (size.isFull) {
|
|
77
|
+
this.lastInsert = this.nFull;
|
|
78
|
+
this.nFull++;
|
|
79
|
+
return new PackLocation(this.lastInsert, 0);
|
|
80
|
+
}
|
|
81
|
+
const index = this.chooseInsertLocation(size.size);
|
|
82
|
+
if (index >= this.loads.length) {
|
|
83
|
+
this.loads.push(0);
|
|
84
|
+
}
|
|
85
|
+
const load = this.loads[index];
|
|
86
|
+
this.loads[index] += size.size;
|
|
87
|
+
return new PackLocation(index, load);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function packing(maxSize: number, arr: DataSize[]): [number, PackLocation[]] {
|
|
92
|
+
const sortedArr = sortDataSizes(arr);
|
|
93
|
+
const inserts: [number, PackLocation][] = [];
|
|
94
|
+
const state = new PackingState(maxSize);
|
|
95
|
+
sortedArr.forEach((value) => {
|
|
96
|
+
const pack = state.insert(value);
|
|
97
|
+
inserts.push([value.index, pack]);
|
|
98
|
+
})
|
|
99
|
+
const nPacks = state.nFull + state.loads.length;
|
|
100
|
+
inserts.sort((left, right) => left[0] - right[0]);
|
|
101
|
+
const res = inserts.map((arg) => arg[1]);
|
|
102
|
+
return [nPacks, res];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const ARCIS_PACKING_SIZE = 214;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Pack field sizes into minimum slots using the Arcium packing size (214 bits).
|
|
109
|
+
* @param arr - Array of field size descriptors.
|
|
110
|
+
* @returns Tuple of [total slot count, pack location for each input field].
|
|
111
|
+
* @internal
|
|
112
|
+
*/
|
|
113
|
+
export function arcisPacking(arr: DataSize[]): [number, PackLocation[]] {
|
|
114
|
+
return packing(ARCIS_PACKING_SIZE, arr);
|
|
115
|
+
}
|
package/src/callback.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PublicKey,
|
|
3
|
+
Finality,
|
|
4
|
+
} from '@solana/web3.js';
|
|
5
|
+
import {
|
|
6
|
+
AnchorProvider, BN,
|
|
7
|
+
} from '@coral-xyz/anchor';
|
|
8
|
+
import { getArciumProgram } from './onchain.js';
|
|
9
|
+
import { getComputationAccAddress, getMXEAccAddress } from './pda.js';
|
|
10
|
+
|
|
11
|
+
const POLL_INTERVAL_MS = 500;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Wait for a computation to finalize by polling the computation account
|
|
15
|
+
* status via HTTP RPC. Does not use WebSocket subscriptions.
|
|
16
|
+
*
|
|
17
|
+
* Polls every 500ms (same as Agave's send_and_confirm_transaction_with_config).
|
|
18
|
+
* Return the most recent transaction signature on the computation account
|
|
19
|
+
* once finalization is detected.
|
|
20
|
+
*
|
|
21
|
+
* @param provider - Anchor provider.
|
|
22
|
+
* @param computationOffset - Computation offset to wait for.
|
|
23
|
+
* @param mxeProgramId - MXE program public key.
|
|
24
|
+
* @param commitment - Commitment level for RPC calls (default: 'confirmed').
|
|
25
|
+
* @param timeoutMs - Maximum wait time in milliseconds (default: 120000).
|
|
26
|
+
* @returns Transaction signature from the finalization.
|
|
27
|
+
* @throws Error if the MXE account has no cluster assigned.
|
|
28
|
+
* @throws Error if the computation does not finalize within timeoutMs.
|
|
29
|
+
*/
|
|
30
|
+
export async function awaitComputationFinalization(
|
|
31
|
+
provider: AnchorProvider,
|
|
32
|
+
computationOffset: BN,
|
|
33
|
+
mxeProgramId: PublicKey,
|
|
34
|
+
commitment: Finality = 'confirmed',
|
|
35
|
+
timeoutMs: number = 120_000,
|
|
36
|
+
): Promise<string> {
|
|
37
|
+
const conn = provider.connection;
|
|
38
|
+
const arciumProgram = getArciumProgram(provider);
|
|
39
|
+
|
|
40
|
+
// Derive computation account PDA (requires clusterOffset from MXE account)
|
|
41
|
+
const mxeAccAddress = getMXEAccAddress(mxeProgramId);
|
|
42
|
+
const mxeAcc = await arciumProgram.account.mxeAccount.fetch(mxeAccAddress, commitment);
|
|
43
|
+
if (mxeAcc.cluster === null) {
|
|
44
|
+
throw new Error('MXE account has no cluster assigned');
|
|
45
|
+
}
|
|
46
|
+
const compAccAddress = getComputationAccAddress(mxeAcc.cluster, computationOffset);
|
|
47
|
+
|
|
48
|
+
const startTime = Date.now();
|
|
49
|
+
let lastStatus: 'unknown' | 'queued' | 'finalized' = 'unknown';
|
|
50
|
+
let rpcErrorCount = 0;
|
|
51
|
+
let lastError: string | undefined;
|
|
52
|
+
|
|
53
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
54
|
+
let compAcc;
|
|
55
|
+
try {
|
|
56
|
+
// eslint-disable-next-line no-await-in-loop
|
|
57
|
+
compAcc = await arciumProgram.account.computationAccount.fetchNullable(
|
|
58
|
+
compAccAddress, commitment,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
rpcErrorCount++;
|
|
63
|
+
lastError = e instanceof Error ? e.message : String(e);
|
|
64
|
+
// eslint-disable-next-line no-await-in-loop
|
|
65
|
+
await new Promise((r) => {
|
|
66
|
+
setTimeout(r, POLL_INTERVAL_MS);
|
|
67
|
+
});
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (compAcc !== null && 'finalized' in compAcc.status) {
|
|
72
|
+
lastStatus = 'finalized';
|
|
73
|
+
for (let sigRetry = 0; sigRetry < 10; sigRetry++) {
|
|
74
|
+
// eslint-disable-next-line no-await-in-loop
|
|
75
|
+
const sigs = await conn.getSignaturesForAddress(
|
|
76
|
+
compAccAddress, { limit: 1 }, commitment,
|
|
77
|
+
);
|
|
78
|
+
if (sigs.length > 0) return sigs[0].signature;
|
|
79
|
+
// eslint-disable-next-line no-await-in-loop
|
|
80
|
+
await new Promise((r) => {
|
|
81
|
+
setTimeout(r, POLL_INTERVAL_MS);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
throw new Error(
|
|
85
|
+
'Computation finalized but transaction signature not indexed after retries.',
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (compAcc !== null) lastStatus = 'queued';
|
|
90
|
+
|
|
91
|
+
// eslint-disable-next-line no-await-in-loop
|
|
92
|
+
await new Promise((r) => {
|
|
93
|
+
setTimeout(r, POLL_INTERVAL_MS);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const timeoutMsg = `Computation did not finalize within ${timeoutMs}ms (status: ${lastStatus})`;
|
|
98
|
+
throw rpcErrorCount > 0
|
|
99
|
+
? new Error(timeoutMsg, { cause: new Error(`${rpcErrorCount} RPC errors, last: ${lastError}`) })
|
|
100
|
+
: new Error(timeoutMsg);
|
|
101
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed for ClockAccount PDA.
|
|
3
|
+
* @constant {string}
|
|
4
|
+
*/
|
|
5
|
+
export const CLOCK_ACC_SEED = 'ClockAccount';
|
|
6
|
+
/**
|
|
7
|
+
* Seed for FeePool PDA.
|
|
8
|
+
* @constant {string}
|
|
9
|
+
*/
|
|
10
|
+
export const POOL_ACC_SEED = 'FeePool';
|
|
11
|
+
/**
|
|
12
|
+
* Seed for ComputationAccount PDA.
|
|
13
|
+
* @constant {string}
|
|
14
|
+
*/
|
|
15
|
+
export const COMPUTATION_ACC_SEED = 'ComputationAccount';
|
|
16
|
+
/**
|
|
17
|
+
* Seed for Mempool PDA.
|
|
18
|
+
* @constant {string}
|
|
19
|
+
*/
|
|
20
|
+
export const MEMPOOL_ACC_SEED = 'Mempool';
|
|
21
|
+
/**
|
|
22
|
+
* Seed for ExecutingPoolAccount PDA.
|
|
23
|
+
* @constant {string}
|
|
24
|
+
*/
|
|
25
|
+
export const EXEC_POOL_ACC_SEED = 'Execpool';
|
|
26
|
+
/**
|
|
27
|
+
* Seed for ClusterAccount PDA.
|
|
28
|
+
* @constant {string}
|
|
29
|
+
*/
|
|
30
|
+
export const CLUSTER_ACC_SEED = 'Cluster';
|
|
31
|
+
/**
|
|
32
|
+
* Seed for ArxNodeAccount PDA.
|
|
33
|
+
* @constant {string}
|
|
34
|
+
*/
|
|
35
|
+
export const ARX_NODE_ACC_SEED = 'ArxNode';
|
|
36
|
+
/**
|
|
37
|
+
* Seed for MXE Account PDA.
|
|
38
|
+
* @constant {string}
|
|
39
|
+
*/
|
|
40
|
+
export const MXE_ACCOUNT_SEED = 'MXEAccount';
|
|
41
|
+
/**
|
|
42
|
+
* Seed for CompDefAccount PDA.
|
|
43
|
+
* @constant {string}
|
|
44
|
+
*/
|
|
45
|
+
export const COMP_DEF_ACC_SEED = 'ComputationDefinitionAccount';
|
|
46
|
+
/**
|
|
47
|
+
* Seed for RecoveryClusterAccount PDA.
|
|
48
|
+
* @constant {string}
|
|
49
|
+
*/
|
|
50
|
+
export const RECOVERY_CLUSTER_ACC_SEED = 'RecoveryClusterAccount';
|
|
51
|
+
/**
|
|
52
|
+
* Seed for MxeRecoveryAccount PDA.
|
|
53
|
+
* @constant {string}
|
|
54
|
+
*/
|
|
55
|
+
export const MXE_RECOVERY_ACC_SEED = 'MxeRecoveryAccount';
|
|
56
|
+
/**
|
|
57
|
+
* Seed for ComputationDefinitionRaw PDA.
|
|
58
|
+
* @constant {string}
|
|
59
|
+
*/
|
|
60
|
+
export const RAW_CIRCUIT_ACC_SEED = 'ComputationDefinitionRaw';
|
|
61
|
+
/**
|
|
62
|
+
* Maximum number of bytes that can be reallocated per instruction.
|
|
63
|
+
* @constant {number}
|
|
64
|
+
*/
|
|
65
|
+
export const MAX_REALLOC_PER_IX = 10240;
|
|
66
|
+
/**
|
|
67
|
+
* Maximum number of bytes that can be uploaded in a single transaction with the upload instruction.
|
|
68
|
+
* @constant {number}
|
|
69
|
+
*/
|
|
70
|
+
export const MAX_UPLOAD_PER_TX_BYTES = 814;
|
|
71
|
+
/**
|
|
72
|
+
* Maximum size of an account in bytes (10MB = 10 * 1024 * 1024).
|
|
73
|
+
* @constant {number}
|
|
74
|
+
*/
|
|
75
|
+
export const MAX_ACCOUNT_SIZE = 10485760;
|
|
76
|
+
/**
|
|
77
|
+
* Maximum number of Arcium embiggen instructions allowed in a single transaction (due to compute unit limits).
|
|
78
|
+
* @constant {number}
|
|
79
|
+
*/
|
|
80
|
+
export const MAX_EMBIGGEN_IX_PER_TX = 18;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Size of account discriminator in bytes.
|
|
84
|
+
* @constant {number}
|
|
85
|
+
*/
|
|
86
|
+
export const DISCRIMINATOR_SIZE = 8;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Size of offset buffer in bytes (u32).
|
|
90
|
+
* @constant {number}
|
|
91
|
+
*/
|
|
92
|
+
export const OFFSET_BUFFER_SIZE = 4;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Size of computation definition offset slice in bytes.
|
|
96
|
+
* @constant {number}
|
|
97
|
+
*/
|
|
98
|
+
export const COMP_DEF_OFFSET_SIZE = 4;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Size of a uint128 in bytes.
|
|
102
|
+
* @constant {number}
|
|
103
|
+
*/
|
|
104
|
+
export const UINT128_BYTE_SIZE = 16;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AesCtrCipher } from './aesCtrCipher.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AES-128 cipher in Counter (CTR) mode, using SHA3-256 to derive the key from a shared secret.
|
|
5
|
+
* See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf (Section 6.5) for details on CTR mode.
|
|
6
|
+
*/
|
|
7
|
+
export class Aes128Cipher extends AesCtrCipher {
|
|
8
|
+
/**
|
|
9
|
+
* Construct an AES-128 cipher instance using a shared secret.
|
|
10
|
+
* The key is derived using SHA3-256.
|
|
11
|
+
* @param sharedSecret - Shared secret to derive the AES key from.
|
|
12
|
+
*/
|
|
13
|
+
constructor(sharedSecret: Uint8Array) {
|
|
14
|
+
super(sharedSecret, 128);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AesCtrCipher } from './aesCtrCipher.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AES-192 cipher in Counter (CTR) mode, using SHA3-256 to derive the key from a shared secret.
|
|
5
|
+
* See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf (Section 6.5) for details on CTR mode.
|
|
6
|
+
*/
|
|
7
|
+
export class Aes192Cipher extends AesCtrCipher {
|
|
8
|
+
/**
|
|
9
|
+
* Construct an AES-192 cipher instance using a shared secret.
|
|
10
|
+
* The key is derived using SHA3-256.
|
|
11
|
+
* @param sharedSecret - Shared secret to derive the AES key from.
|
|
12
|
+
*/
|
|
13
|
+
constructor(sharedSecret: Uint8Array) {
|
|
14
|
+
super(sharedSecret, 192);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AesCtrCipher } from './aesCtrCipher.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AES-256 cipher in Counter (CTR) mode, using SHA3-256 to derive the key from a shared secret.
|
|
5
|
+
* See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf (Section 6.5) for details on CTR mode.
|
|
6
|
+
*/
|
|
7
|
+
export class Aes256Cipher extends AesCtrCipher {
|
|
8
|
+
/**
|
|
9
|
+
* Construct an AES-256 cipher instance using a shared secret.
|
|
10
|
+
* The key is derived using SHA3-256.
|
|
11
|
+
* @param sharedSecret - Shared secret to derive the AES key from.
|
|
12
|
+
*/
|
|
13
|
+
constructor(sharedSecret: Uint8Array) {
|
|
14
|
+
super(sharedSecret, 256);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createHash, createCipheriv, createDecipheriv } from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Supported AES key sizes in bits.
|
|
5
|
+
*/
|
|
6
|
+
export type AesKeyBits = 128 | 192 | 256;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Mapping from key bits to key byte length.
|
|
10
|
+
*/
|
|
11
|
+
const KEY_BYTES: Record<AesKeyBits, number> = { 128: 16, 192: 24, 256: 32 };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generic AES cipher in Counter (CTR) mode, using SHA3-256 to derive the key from a shared secret.
|
|
15
|
+
* See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf (Section 6.5) for details on CTR mode.
|
|
16
|
+
*/
|
|
17
|
+
export class AesCtrCipher {
|
|
18
|
+
protected key: Uint8Array;
|
|
19
|
+
private readonly keyBits: AesKeyBits;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Construct an AES cipher instance using a shared secret.
|
|
23
|
+
* The key is derived using SHA3-256.
|
|
24
|
+
* @param sharedSecret - Shared secret to derive the AES key from.
|
|
25
|
+
* @param keyBits - AES key size in bits (128, 192, or 256).
|
|
26
|
+
*/
|
|
27
|
+
constructor(sharedSecret: Uint8Array, keyBits: AesKeyBits) {
|
|
28
|
+
this.keyBits = keyBits;
|
|
29
|
+
const hasher = createHash('sha3-256');
|
|
30
|
+
// We follow [Section 4, Option 1.](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf).
|
|
31
|
+
// For our choice of hash function, we have:
|
|
32
|
+
// - H_outputBits = 256
|
|
33
|
+
// - max_H_inputBits = arbitrarily long, as SHA3 is built upon the sponge
|
|
34
|
+
// construction
|
|
35
|
+
// - L = keyBits (128, 192, or 256).
|
|
36
|
+
|
|
37
|
+
// Build the vector `counter || Z || FixedInfo` (we only have i = 1, since reps = 1).
|
|
38
|
+
// the counter is a big-endian 4-byte unsigned integer
|
|
39
|
+
const counter = [0, 0, 0, 1];
|
|
40
|
+
for (let i = 0; i < sharedSecret.length; ++i) {
|
|
41
|
+
counter.push(sharedSecret[i]);
|
|
42
|
+
}
|
|
43
|
+
// For the FixedInfo we simply take L. We represent L as a big-endian 2-byte
|
|
44
|
+
// unsigned integer.
|
|
45
|
+
counter.push(Math.floor(keyBits / 256));
|
|
46
|
+
counter.push(keyBits % 256);
|
|
47
|
+
hasher.update(new Uint8Array(counter));
|
|
48
|
+
this.key = new Uint8Array(hasher.digest()).slice(0, KEY_BYTES[keyBits]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Encrypt the plaintext array in Counter (CTR) mode.
|
|
53
|
+
* @param plaintext - Data to encrypt.
|
|
54
|
+
* @param nonce - 8-byte nonce for CTR mode.
|
|
55
|
+
* @returns Encrypted ciphertext as a Uint8Array.
|
|
56
|
+
* @throws Error if the nonce is not 8 bytes long.
|
|
57
|
+
*/
|
|
58
|
+
encrypt(plaintext: Uint8Array, nonce: Uint8Array): Uint8Array {
|
|
59
|
+
if (nonce.length !== 8) {
|
|
60
|
+
throw Error(`nonce must be of length 8 (found ${nonce.length})`);
|
|
61
|
+
}
|
|
62
|
+
const paddedNonce = Buffer.concat([nonce, Buffer.alloc(16 - nonce.length, 0)]);
|
|
63
|
+
const cipher = createCipheriv(`aes-${this.keyBits}-ctr`, this.key, paddedNonce);
|
|
64
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
65
|
+
return new Uint8Array(ciphertext);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Decrypt the ciphertext array in Counter (CTR) mode.
|
|
70
|
+
* @param ciphertext - Data to decrypt.
|
|
71
|
+
* @param nonce - 8-byte nonce for CTR mode.
|
|
72
|
+
* @returns Decrypted plaintext as a Uint8Array.
|
|
73
|
+
* @throws Error if the nonce is not 8 bytes long.
|
|
74
|
+
*/
|
|
75
|
+
decrypt(ciphertext: Uint8Array, nonce: Uint8Array): Uint8Array {
|
|
76
|
+
if (nonce.length !== 8) {
|
|
77
|
+
throw Error(`nonce must be of length 8 (found ${nonce.length})`);
|
|
78
|
+
}
|
|
79
|
+
const paddedNonce = Buffer.concat([nonce, Buffer.alloc(16 - nonce.length, 0)]);
|
|
80
|
+
const cipher = createDecipheriv(`aes-${this.keyBits}-ctr`, this.key, paddedNonce);
|
|
81
|
+
const decrypted = Buffer.concat([cipher.update(ciphertext), cipher.final()]);
|
|
82
|
+
return new Uint8Array(decrypted);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
2
|
+
import { randomBytes } from '@noble/hashes/utils';
|
|
3
|
+
import { CurveFn, twistedEdwards } from '@noble/curves/abstract/edwards';
|
|
4
|
+
import { isNegativeLE, mod, pow2 } from '@noble/curves/abstract/modular';
|
|
5
|
+
// to please the linter
|
|
6
|
+
import { sha3_512 as sha3 } from '@noble/hashes/sha3';
|
|
7
|
+
|
|
8
|
+
// The arcisEd25519 signature scheme. This is essentially ed25519 but we use the hash function
|
|
9
|
+
// SHA3-512 instead of SHA-512 since its multiplicative depth is much lower, which
|
|
10
|
+
// makes it much better suited to be evaluated in MPC.
|
|
11
|
+
|
|
12
|
+
// Those are the parameters specified [here](https://datatracker.ietf.org/doc/html/rfc8032#section-5.1)
|
|
13
|
+
// (except for the hash function, see above). The below is copied from [here](https://github.com/paulmillr/noble-curves/blob/main/src/ed25519.ts).
|
|
14
|
+
const arcisEd25519Defaults = (() => ({
|
|
15
|
+
a: BigInt('57896044618658097711785492504343953926634992332820282019728792003956564819948'),
|
|
16
|
+
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'),
|
|
17
|
+
Fp: ed25519.CURVE.Fp,
|
|
18
|
+
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),
|
|
19
|
+
h: BigInt(8),
|
|
20
|
+
Gx: BigInt('15112221349535400772501151409588531511454012693041857206046113283949847762202'),
|
|
21
|
+
Gy: BigInt('46316835694926478169428394003475163141307993866256225615783033603165251855960'),
|
|
22
|
+
hash: sha3,
|
|
23
|
+
randomBytes,
|
|
24
|
+
adjustScalarBytes,
|
|
25
|
+
uvRatio,
|
|
26
|
+
}) as const)();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Ed25519 curve instance using SHA3-512 for hashing, suitable for MPC (ArcisEd25519 signature scheme).
|
|
30
|
+
* This is essentially Ed25519 but with SHA3-512 instead of SHA-512 for lower multiplicative depth.
|
|
31
|
+
* See: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1
|
|
32
|
+
*/
|
|
33
|
+
export const arcisEd25519: CurveFn = (() => twistedEdwards(arcisEd25519Defaults))();
|
|
34
|
+
|
|
35
|
+
// Helper function for the sqrt in Fp.
|
|
36
|
+
function ed25519_pow_2_252_3(x: bigint) {
|
|
37
|
+
// prettier-ignore
|
|
38
|
+
const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80);
|
|
39
|
+
const P = ed25519.CURVE.Fp.ORDER;
|
|
40
|
+
const x2 = (x * x) % P;
|
|
41
|
+
const b2 = (x2 * x) % P; // x^3, 11
|
|
42
|
+
const b4 = (pow2(b2, 2n, P) * b2) % P; // x^15, 1111
|
|
43
|
+
const b5 = (pow2(b4, 1n, P) * x) % P; // x^31
|
|
44
|
+
const b10 = (pow2(b5, 5n, P) * b5) % P;
|
|
45
|
+
const b20 = (pow2(b10, _10n, P) * b10) % P;
|
|
46
|
+
const b40 = (pow2(b20, _20n, P) * b20) % P;
|
|
47
|
+
const b80 = (pow2(b40, _40n, P) * b40) % P;
|
|
48
|
+
const b160 = (pow2(b80, _80n, P) * b80) % P;
|
|
49
|
+
const b240 = (pow2(b160, _80n, P) * b80) % P;
|
|
50
|
+
const b250 = (pow2(b240, _10n, P) * b10) % P;
|
|
51
|
+
const pow_p_5_8 = (pow2(b250, 2n, P) * x) % P;
|
|
52
|
+
// ^ To pow to (p+3)/8, multiply it by x.
|
|
53
|
+
return { pow_p_5_8, b2 };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fp.sqrt(Fp.neg(1))
|
|
57
|
+
const ED25519_SQRT_M1 = /* @__PURE__ */ BigInt(
|
|
58
|
+
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Clamp a 32-byte scalar as required by the Ed25519 signature scheme.
|
|
63
|
+
* See: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5
|
|
64
|
+
* @param bytes - 32-byte scalar to clamp.
|
|
65
|
+
* @returns Clamped scalar as a Uint8Array.
|
|
66
|
+
*/
|
|
67
|
+
function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
|
|
68
|
+
const clamped = bytes;
|
|
69
|
+
clamped[0] &= 248;
|
|
70
|
+
clamped[31] &= 127;
|
|
71
|
+
clamped[31] |= 64;
|
|
72
|
+
return clamped;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Helper function for decompression, calculating √(u/v).
|
|
77
|
+
* @returns Object with isValid and value.
|
|
78
|
+
*/
|
|
79
|
+
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
|
|
80
|
+
const P = ed25519.CURVE.Fp.ORDER;
|
|
81
|
+
const v3 = mod(v * v * v, P); // v³
|
|
82
|
+
const v7 = mod(v3 * v3 * v, P); // v⁷
|
|
83
|
+
// (p+3)/8 and (p-5)/8
|
|
84
|
+
const pow = ed25519_pow_2_252_3(u * v7).pow_p_5_8;
|
|
85
|
+
let x = mod(u * v3 * pow, P); // (uv³)(uv⁷)^(p-5)/8
|
|
86
|
+
const vx2 = mod(v * x * x, P); // vx²
|
|
87
|
+
const root1 = x; // First root candidate
|
|
88
|
+
const root2 = mod(x * ED25519_SQRT_M1, P); // Second root candidate
|
|
89
|
+
const useRoot1 = vx2 === u; // If vx² = u (mod p), x is a square root
|
|
90
|
+
const useRoot2 = vx2 === mod(-u, P); // If vx² = -u, set x <-- x * 2^((p-1)/4)
|
|
91
|
+
const noRoot = vx2 === mod(-u * ED25519_SQRT_M1, P); // There is no valid root, vx² = -u√(-1)
|
|
92
|
+
if (useRoot1) x = root1;
|
|
93
|
+
if (useRoot2 || noRoot) x = root2; // We return root2 anyway, for const-time
|
|
94
|
+
if (isNegativeLE(x, P)) x = mod(-x, P);
|
|
95
|
+
return { isValid: useRoot1 || useRoot2, value: x };
|
|
96
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CURVE25519_SCALAR_FIELD,
|
|
3
|
+
} from './rescueDesc.js';
|
|
4
|
+
import { RescueCipherCommon } from './rescueCipherCommon.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The Rescue cipher over Curve25519's scalar field in Counter (CTR) mode, with a fixed block size m = 5.
|
|
8
|
+
* See: https://tosc.iacr.org/index.php/ToSC/article/view/8695/8287
|
|
9
|
+
*/
|
|
10
|
+
export class CSplRescueCipher {
|
|
11
|
+
cipher: RescueCipherCommon;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Construct a CSplRescueCipher instance using a shared secret.
|
|
15
|
+
* The key is derived using RescuePrimeHash and used to initialize the RescueDesc.
|
|
16
|
+
* @param sharedSecret - Shared secret to derive the cipher key from.
|
|
17
|
+
*/
|
|
18
|
+
constructor(sharedSecret: Uint8Array) {
|
|
19
|
+
this.cipher = new RescueCipherCommon(sharedSecret, CURVE25519_SCALAR_FIELD);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Encrypt the plaintext vector in Counter (CTR) mode and serialize each block.
|
|
24
|
+
* @param plaintext - Array of plaintext bigints to encrypt.
|
|
25
|
+
* @param nonce - 16-byte nonce for CTR mode.
|
|
26
|
+
* @returns Ciphertext as an array of arrays of numbers (each 32 bytes).
|
|
27
|
+
*/
|
|
28
|
+
encrypt(plaintext: bigint[], nonce: Uint8Array): number[][] {
|
|
29
|
+
return this.cipher.encrypt(plaintext, nonce);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Deserialize and decrypt the ciphertext vector in Counter (CTR) mode.
|
|
34
|
+
* @param ciphertext - Array of arrays of numbers (each 32 bytes) to decrypt.
|
|
35
|
+
* @param nonce - 16-byte nonce for CTR mode.
|
|
36
|
+
* @returns Decrypted plaintext as an array of bigints.
|
|
37
|
+
*/
|
|
38
|
+
decrypt(ciphertext: number[][], nonce: Uint8Array): bigint[] {
|
|
39
|
+
return this.cipher.decrypt(ciphertext, nonce);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'crypto';
|
|
2
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Scalar field prime modulus for Curve25519: 2^252 + 27742317777372353535851937790883648493
|
|
6
|
+
*/
|
|
7
|
+
export const CURVE25519_SCALAR_FIELD_MODULUS = ed25519.CURVE.n;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate a random value within the field bound by q.
|
|
11
|
+
* @param q - Upper bound (exclusive) for the random value.
|
|
12
|
+
* @returns Random bigint value between 0 and q-1.
|
|
13
|
+
*/
|
|
14
|
+
export function generateRandomFieldElem(q: bigint): bigint {
|
|
15
|
+
const byteLength = (q.toString(2).length + 7) >> 3;
|
|
16
|
+
let r: bigint;
|
|
17
|
+
do {
|
|
18
|
+
const randomBuffer = randomBytes(byteLength);
|
|
19
|
+
r = BigInt(`0x${randomBuffer.toString('hex')}`);
|
|
20
|
+
} while (r >= q);
|
|
21
|
+
return r;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Compute the positive modulo of a over m.
|
|
26
|
+
* @param a - Dividend.
|
|
27
|
+
* @param m - Modulus.
|
|
28
|
+
* @returns Positive remainder of a mod m.
|
|
29
|
+
*/
|
|
30
|
+
export function positiveModulo(a: bigint, m: bigint): bigint {
|
|
31
|
+
return ((a % m) + m) % m;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Serialize a bigint to a little-endian Uint8Array of the specified length.
|
|
36
|
+
* @param val - Bigint value to serialize.
|
|
37
|
+
* @param lengthInBytes - Desired length of the output array.
|
|
38
|
+
* @returns Serialized value as a Uint8Array.
|
|
39
|
+
* @throws Error if the value is too large for the specified length.
|
|
40
|
+
*/
|
|
41
|
+
export function serializeLE(val: bigint, lengthInBytes: number): Uint8Array {
|
|
42
|
+
const result = new Uint8Array(lengthInBytes);
|
|
43
|
+
let tempVal = val;
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < lengthInBytes; i++) {
|
|
46
|
+
result[i] = Number(tempVal & BigInt(255));
|
|
47
|
+
tempVal >>= BigInt(8);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (tempVal > BigInt(0)) {
|
|
51
|
+
throw new Error(`Value ${val} is too large for the byte length ${lengthInBytes}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Deserialize a little-endian Uint8Array to a bigint.
|
|
59
|
+
* @param bytes - Uint8Array to deserialize.
|
|
60
|
+
* @returns Deserialized bigint value.
|
|
61
|
+
*/
|
|
62
|
+
export function deserializeLE(bytes: Uint8Array): bigint {
|
|
63
|
+
let result = BigInt(0);
|
|
64
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
65
|
+
result |= BigInt(bytes[i]) << (BigInt(i) * BigInt(8));
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// GENERAL
|
|
71
|
+
/**
|
|
72
|
+
* Compute the SHA-256 hash of an array of Uint8Arrays.
|
|
73
|
+
* @param byteArrays - Arrays to hash.
|
|
74
|
+
* @returns SHA-256 hash as a Buffer.
|
|
75
|
+
*/
|
|
76
|
+
export function sha256(byteArrays: Uint8Array[]): Buffer {
|
|
77
|
+
const hash = createHash('sha256');
|
|
78
|
+
byteArrays.forEach((byteArray) => {
|
|
79
|
+
hash.update(byteArray);
|
|
80
|
+
});
|
|
81
|
+
return hash.digest();
|
|
82
|
+
}
|