@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,492 @@
|
|
|
1
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
2
|
+
import { shake256 } from '@noble/hashes/sha3';
|
|
3
|
+
import { IField, invert } from '@noble/curves/abstract/modular';
|
|
4
|
+
import { Matrix } from '../matrix.js';
|
|
5
|
+
import { deserializeLE } from './cryptography.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents the operational mode for the Rescue cryptographic primitive.
|
|
9
|
+
* Can be either a block cipher mode with a key, or a hash function mode with parameters.
|
|
10
|
+
*/
|
|
11
|
+
type RescueMode = BlockCipher | HashFunction;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Block cipher mode configuration for Rescue.
|
|
15
|
+
* Use a key for encryption/decryption operations.
|
|
16
|
+
*/
|
|
17
|
+
type BlockCipher = { kind: 'cipher'; key: bigint[] };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hash function mode configuration for Rescue.
|
|
21
|
+
* @param m - Rate (number of field elements absorbed per round).
|
|
22
|
+
* @param capacity - Capacity (number of field elements in the state that are not directly accessible).
|
|
23
|
+
*/
|
|
24
|
+
type HashFunction = { kind: 'hash'; m: number; capacity: number };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Field type.
|
|
28
|
+
*/
|
|
29
|
+
export type FpField = IField<bigint>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Curve25519 base field as an IField instance.
|
|
33
|
+
*/
|
|
34
|
+
export const CURVE25519_BASE_FIELD: FpField = ed25519.Point.Fp;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Curve25519 scalar field as an IField instance.
|
|
38
|
+
*/
|
|
39
|
+
export const CURVE25519_SCALAR_FIELD: FpField = ed25519.Point.Fn;
|
|
40
|
+
|
|
41
|
+
// Security level for the block cipher.
|
|
42
|
+
const SECURITY_LEVEL_BLOCK_CIPHER = 128;
|
|
43
|
+
|
|
44
|
+
// Security level for the hash function.
|
|
45
|
+
const SECURITY_LEVEL_HASH_FUNCTION = 256;
|
|
46
|
+
|
|
47
|
+
// We refer to https://tosc.iacr.org/index.php/ToSC/article/view/8695/8287 for more details.
|
|
48
|
+
/**
|
|
49
|
+
* Description and parameters for the Rescue cipher or hash function, including round constants, MDS matrix, and key schedule.
|
|
50
|
+
* See: https://tosc.iacr.org/index.php/ToSC/article/view/8695/8287
|
|
51
|
+
*/
|
|
52
|
+
export class RescueDesc {
|
|
53
|
+
mode: RescueMode;
|
|
54
|
+
|
|
55
|
+
field: FpField;
|
|
56
|
+
|
|
57
|
+
// The smallest prime that does not divide p-1.
|
|
58
|
+
alpha: bigint;
|
|
59
|
+
|
|
60
|
+
// The inverse of alpha modulo p-1.
|
|
61
|
+
alphaInverse: bigint;
|
|
62
|
+
|
|
63
|
+
nRounds: number;
|
|
64
|
+
|
|
65
|
+
m: number;
|
|
66
|
+
|
|
67
|
+
// A Maximum Distance Separable matrix.
|
|
68
|
+
mdsMat: Matrix;
|
|
69
|
+
|
|
70
|
+
// Its inverse.
|
|
71
|
+
mdsMatInverse: Matrix;
|
|
72
|
+
|
|
73
|
+
// The round keys, needed for encryption and decryption.
|
|
74
|
+
roundKeys: Matrix[];
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Construct a RescueDesc for a given field and mode (cipher or hash).
|
|
78
|
+
* Initialize round constants, MDS matrix, and key schedule.
|
|
79
|
+
* @param field - Field to use (e.g., CURVE25519_BASE_FIELD).
|
|
80
|
+
* @param mode - Mode: block cipher or hash function.
|
|
81
|
+
*/
|
|
82
|
+
constructor(field: FpField, mode: RescueMode) {
|
|
83
|
+
this.field = field;
|
|
84
|
+
this.mode = mode;
|
|
85
|
+
switch (this.mode.kind) {
|
|
86
|
+
case 'cipher': {
|
|
87
|
+
this.m = this.mode.key.length;
|
|
88
|
+
if (this.m < 2) {
|
|
89
|
+
throw Error(`parameter m must be at least 2 (found ${this.m})`);
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case 'hash': {
|
|
94
|
+
this.m = this.mode.m;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
default: {
|
|
98
|
+
this.m = 0;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const alphaAndInverse = getAlphaAndInverse(this.field.ORDER);
|
|
103
|
+
this.alpha = alphaAndInverse[0];
|
|
104
|
+
this.alphaInverse = alphaAndInverse[1];
|
|
105
|
+
this.nRounds = getNRounds(this.field.ORDER, this.mode, this.alpha, this.m);
|
|
106
|
+
const mdsMatrixAndInverse = getMdsMatrixAndInverse(this.field, this.m);
|
|
107
|
+
this.mdsMat = mdsMatrixAndInverse[0];
|
|
108
|
+
this.mdsMatInverse = mdsMatrixAndInverse[1];
|
|
109
|
+
|
|
110
|
+
// generate the round constants using SHAKE256
|
|
111
|
+
const roundConstants = this.sampleConstants(this.nRounds);
|
|
112
|
+
|
|
113
|
+
switch (this.mode.kind) {
|
|
114
|
+
case 'cipher': {
|
|
115
|
+
// do the key schedule
|
|
116
|
+
this.roundKeys = rescuePermutation(
|
|
117
|
+
this.mode,
|
|
118
|
+
this.alpha,
|
|
119
|
+
this.alphaInverse,
|
|
120
|
+
this.mdsMat,
|
|
121
|
+
roundConstants,
|
|
122
|
+
new Matrix(this.field, toVec(this.mode.key)),
|
|
123
|
+
);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'hash': {
|
|
127
|
+
this.roundKeys = roundConstants;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
default: {
|
|
131
|
+
this.roundKeys = [];
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Sample round constants for the Rescue permutation, using SHAKE256.
|
|
139
|
+
* @param nRounds - Number of rounds.
|
|
140
|
+
* @returns Array of round constant matrices.
|
|
141
|
+
*/
|
|
142
|
+
sampleConstants(nRounds: number): Matrix[] {
|
|
143
|
+
const field = this.field;
|
|
144
|
+
const m = this.m;
|
|
145
|
+
|
|
146
|
+
// setup randomness
|
|
147
|
+
// dkLen is the output length from the Keccak instance behind shake.
|
|
148
|
+
// this is irrelevant for our extendable output function (xof), but still we use
|
|
149
|
+
// the default value from one-time shake256 hashing, as defined in shake256's definition
|
|
150
|
+
// in noble-hashes-sha3.
|
|
151
|
+
const hasher = shake256.create({ dkLen: 256 / 8 });
|
|
152
|
+
|
|
153
|
+
// buffer to create field elements from bytes
|
|
154
|
+
// we add 16 bytes to get a distribution statistically close to uniform
|
|
155
|
+
const bufferLen = Math.ceil(field.BITS / 8) + 16;
|
|
156
|
+
|
|
157
|
+
switch (this.mode.kind) {
|
|
158
|
+
case 'cipher': {
|
|
159
|
+
hasher.update('encrypt everything, compute anything');
|
|
160
|
+
|
|
161
|
+
const rFieldArray = Array.from({ length: m * m + 2 * m }, () => {
|
|
162
|
+
// create field element from the shake hash
|
|
163
|
+
const randomness = hasher.xof(bufferLen);
|
|
164
|
+
// we need not check whether the obtained field element f is in any subgroup,
|
|
165
|
+
// because we use only prime fields (i.e. there are no subgroups)
|
|
166
|
+
return field.create(deserializeLE(randomness));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// create matrix and vectors
|
|
170
|
+
const matData = Array.from({ length: m }, () => rFieldArray.splice(0, m));
|
|
171
|
+
let roundConstantMat = new Matrix(field, matData);
|
|
172
|
+
const initData = Array.from({ length: m }, () => rFieldArray.splice(0, 1));
|
|
173
|
+
const initialRoundConstant = new Matrix(field, initData);
|
|
174
|
+
const roundData = Array.from({ length: m }, () => rFieldArray.splice(0, 1));
|
|
175
|
+
const roundConstantAffineTerm = new Matrix(field, roundData);
|
|
176
|
+
|
|
177
|
+
// check for inversability
|
|
178
|
+
while (field.is0(roundConstantMat.det())) {
|
|
179
|
+
// resample matrix
|
|
180
|
+
const resampleArray = Array.from({ length: m * m }, () => {
|
|
181
|
+
const randomness = hasher.xof(bufferLen);
|
|
182
|
+
return field.create(deserializeLE(randomness));
|
|
183
|
+
});
|
|
184
|
+
const resampleData = Array.from({ length: m }, () => resampleArray.splice(0, m));
|
|
185
|
+
roundConstantMat = new Matrix(field, resampleData);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const roundConstants = [initialRoundConstant];
|
|
189
|
+
for (let r = 0; r < 2 * this.nRounds; ++r) {
|
|
190
|
+
roundConstants.push(roundConstantMat.matMul(roundConstants[r]).add(roundConstantAffineTerm));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return roundConstants;
|
|
194
|
+
}
|
|
195
|
+
case 'hash': {
|
|
196
|
+
hasher.update(`Rescue-XLIX(${this.field.ORDER},${m},${this.mode.capacity},${SECURITY_LEVEL_HASH_FUNCTION})`);
|
|
197
|
+
|
|
198
|
+
// this.permute requires an odd number of round keys
|
|
199
|
+
// prepending a 0 matrix makes it equivalent to Algorithm 3 from https://eprint.iacr.org/2020/1143.pdf
|
|
200
|
+
const zeros = [];
|
|
201
|
+
for (let i = 0; i < m; ++i) {
|
|
202
|
+
zeros.push([0n]);
|
|
203
|
+
}
|
|
204
|
+
const roundConstants = [new Matrix(field, zeros)];
|
|
205
|
+
const rFieldArray = Array.from({ length: 2 * m * nRounds }, () => {
|
|
206
|
+
// create field element from the shake hash
|
|
207
|
+
const randomness = hasher.xof(bufferLen);
|
|
208
|
+
// we need not check whether the obtained field element f is in any subgroup,
|
|
209
|
+
// because we use only prime fields (i.e. there are no subgroups)
|
|
210
|
+
return field.create(deserializeLE(randomness));
|
|
211
|
+
});
|
|
212
|
+
for (let r = 0; r < 2 * nRounds; ++r) {
|
|
213
|
+
const data = [];
|
|
214
|
+
for (let i = 0; i < m; ++i) {
|
|
215
|
+
data.push([rFieldArray[r * m + i]]);
|
|
216
|
+
}
|
|
217
|
+
roundConstants.push(new Matrix(field, data));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return roundConstants;
|
|
221
|
+
}
|
|
222
|
+
default: return [];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Apply the Rescue permutation to a state matrix.
|
|
228
|
+
* @param state - Input state matrix.
|
|
229
|
+
* @returns Permuted state matrix.
|
|
230
|
+
*/
|
|
231
|
+
permute(state: Matrix): Matrix {
|
|
232
|
+
return rescuePermutation(
|
|
233
|
+
this.mode,
|
|
234
|
+
this.alpha,
|
|
235
|
+
this.alphaInverse,
|
|
236
|
+
this.mdsMat,
|
|
237
|
+
this.roundKeys,
|
|
238
|
+
state,
|
|
239
|
+
)[2 * this.nRounds];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Apply the inverse Rescue permutation to a state matrix.
|
|
244
|
+
* @param state - Input state matrix.
|
|
245
|
+
* @returns Inverse-permuted state matrix.
|
|
246
|
+
*/
|
|
247
|
+
permuteInverse(state: Matrix): Matrix {
|
|
248
|
+
return rescuePermutationInverse(
|
|
249
|
+
this.mode,
|
|
250
|
+
this.alpha,
|
|
251
|
+
this.alphaInverse,
|
|
252
|
+
this.mdsMatInverse,
|
|
253
|
+
this.roundKeys,
|
|
254
|
+
state,
|
|
255
|
+
)[2 * this.nRounds];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Find the smallest prime alpha that does not divide p-1, and compute its inverse modulo p-1.
|
|
261
|
+
* The alpha parameter is used in the Rescue permutation for exponentiation operations.
|
|
262
|
+
* @param p - Field modulus (prime number).
|
|
263
|
+
* @returns Tuple [alpha, alphaInverse] where alpha is the prime and alphaInverse is its modular inverse.
|
|
264
|
+
* @throws Error if no suitable prime alpha is found.
|
|
265
|
+
*/
|
|
266
|
+
function getAlphaAndInverse(p: bigint): bigint[] {
|
|
267
|
+
const pMinusOne = p - 1n;
|
|
268
|
+
let alpha = 0n;
|
|
269
|
+
for (const a of [2n, 3n, 5n, 7n, 11n, 13n, 17n, 19n, 23n, 29n, 31n, 37n, 41n, 43n, 47n]) {
|
|
270
|
+
if (pMinusOne % a !== 0n) {
|
|
271
|
+
alpha = a;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (alpha === 0n) {
|
|
276
|
+
throw Error('Could not find prime alpha that does not divide p-1.');
|
|
277
|
+
}
|
|
278
|
+
const alphaInverse = invert(alpha, pMinusOne);
|
|
279
|
+
return [alpha, alphaInverse];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Calculate the number of rounds required for the Rescue permutation based on security analysis.
|
|
284
|
+
* The number of rounds is determined by analyzing resistance to differential and algebraic attacks.
|
|
285
|
+
* See: https://tosc.iacr.org/index.php/ToSC/article/view/8695/8287 for the security analysis.
|
|
286
|
+
* @param p - Field modulus.
|
|
287
|
+
* @param mode - Rescue mode (cipher or hash).
|
|
288
|
+
* @param alpha - Prime alpha parameter.
|
|
289
|
+
* @param m - State size (block size for cipher, total size for hash).
|
|
290
|
+
* @returns Number of rounds (will be doubled for the full permutation).
|
|
291
|
+
*/
|
|
292
|
+
function getNRounds(p: bigint, mode: RescueMode, alpha: bigint, m: number): number {
|
|
293
|
+
function dcon(n: number): number {
|
|
294
|
+
return Math.floor(0.5 * (Number(alpha) - 1) * m * (n - 1) + 2.0);
|
|
295
|
+
}
|
|
296
|
+
function v(n: number, rate: number): number {
|
|
297
|
+
return m * (n - 1) + rate;
|
|
298
|
+
}
|
|
299
|
+
function binomial(n: number, k: number): bigint {
|
|
300
|
+
function factorial(x: bigint): bigint {
|
|
301
|
+
if (x === 0n || x === 1n) {
|
|
302
|
+
return 1n;
|
|
303
|
+
}
|
|
304
|
+
return x * factorial(x - 1n);
|
|
305
|
+
}
|
|
306
|
+
return factorial(BigInt(n)) / (factorial(BigInt(n - k)) * factorial(BigInt(k)));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
switch (mode.kind) {
|
|
310
|
+
case 'cipher': {
|
|
311
|
+
const l0 = Math.ceil(
|
|
312
|
+
(2 * SECURITY_LEVEL_BLOCK_CIPHER) / ((m + 1) * (Math.log2(Number(p)) - Math.log2(Number(alpha) - 1))),
|
|
313
|
+
);
|
|
314
|
+
let l1 = 0;
|
|
315
|
+
if (alpha === 3n) {
|
|
316
|
+
l1 = Math.ceil((SECURITY_LEVEL_BLOCK_CIPHER + 2) / (4 * m));
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
l1 = Math.ceil((SECURITY_LEVEL_BLOCK_CIPHER + 3) / (5.5 * m));
|
|
320
|
+
}
|
|
321
|
+
return 2 * Math.max(l0, l1, 5);
|
|
322
|
+
}
|
|
323
|
+
case 'hash': {
|
|
324
|
+
// get number of rounds for Groebner basis attack
|
|
325
|
+
const rate = m - mode.capacity;
|
|
326
|
+
const target = 1n << BigInt(SECURITY_LEVEL_HASH_FUNCTION);
|
|
327
|
+
let l1 = 1;
|
|
328
|
+
let tmp = binomial(v(l1, rate) + dcon(l1), v(l1, rate));
|
|
329
|
+
while (tmp * tmp <= target && l1 <= 23) {
|
|
330
|
+
l1 += 1;
|
|
331
|
+
tmp = binomial(v(l1, rate) + dcon(l1), v(l1, rate));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// set a minimum value for sanity and add 50%
|
|
335
|
+
return Math.ceil(1.5 * Math.max(5, l1));
|
|
336
|
+
}
|
|
337
|
+
default: return 0;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Build a Cauchy matrix for use as an MDS (Maximum Distance Separable) matrix.
|
|
343
|
+
* A Cauchy matrix is guaranteed to be invertible and provides optimal diffusion properties.
|
|
344
|
+
* The matrix is constructed using the formula: M[i][j] = 1/(i + j) for i, j in [1, size].
|
|
345
|
+
* @param field - Finite field over which to construct the matrix.
|
|
346
|
+
* @param size - Size of the square matrix.
|
|
347
|
+
* @returns Cauchy matrix of the specified size.
|
|
348
|
+
*/
|
|
349
|
+
function buildCauchy(field: FpField, size: number): Matrix {
|
|
350
|
+
const data = [];
|
|
351
|
+
for (let i = 1n; i <= size; ++i) {
|
|
352
|
+
const row = [];
|
|
353
|
+
for (let j = 1n; j <= size; ++j) {
|
|
354
|
+
row.push(field.inv(i + j));
|
|
355
|
+
}
|
|
356
|
+
data.push(row);
|
|
357
|
+
}
|
|
358
|
+
return new Matrix(field, data);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Build the inverse of a Cauchy matrix for use as the inverse MDS matrix.
|
|
363
|
+
* The inverse is computed using a closed-form formula for Cauchy matrix inversion.
|
|
364
|
+
* @param field - Finite field over which to construct the matrix.
|
|
365
|
+
* @param size - Size of the square matrix.
|
|
366
|
+
* @returns Inverse of the Cauchy matrix.
|
|
367
|
+
*/
|
|
368
|
+
function buildInverseCauchy(field: FpField, size: number): Matrix {
|
|
369
|
+
function product(arr: bigint[]): bigint {
|
|
370
|
+
return arr.reduce((acc, curr) => field.mul(acc, field.create(curr)), field.ONE);
|
|
371
|
+
}
|
|
372
|
+
function prime(arr: bigint[], val: bigint): bigint {
|
|
373
|
+
return product(
|
|
374
|
+
arr.map(
|
|
375
|
+
(u) => {
|
|
376
|
+
if (u !== val) {
|
|
377
|
+
return val - u;
|
|
378
|
+
}
|
|
379
|
+
return 1n;
|
|
380
|
+
},
|
|
381
|
+
),
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
const data = [];
|
|
385
|
+
for (let i = 1n; i <= size; ++i) {
|
|
386
|
+
const row = [];
|
|
387
|
+
for (let j = 1n; j <= size; ++j) {
|
|
388
|
+
const a = product(Array.from({ length: size }, (_, key) => -i - BigInt(1 + key)));
|
|
389
|
+
const aPrime = prime(Array.from({ length: size }, (_, key) => BigInt(1 + key)), j);
|
|
390
|
+
const b = product(Array.from({ length: size }, (_, key) => j + BigInt(1 + key)));
|
|
391
|
+
const bPrime = prime(Array.from({ length: size }, (_, key) => -BigInt(1 + key)), -i);
|
|
392
|
+
row.push(field.mul(a, field.mul(b, field.mul(field.inv(aPrime), field.mul(field.inv(bPrime), field.inv(-i - j))))));
|
|
393
|
+
}
|
|
394
|
+
data.push(row);
|
|
395
|
+
}
|
|
396
|
+
return new Matrix(field, data);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function getMdsMatrixAndInverse(field: FpField, m: number): Matrix[] {
|
|
400
|
+
const mdsMat = buildCauchy(field, m);
|
|
401
|
+
const mdsMatInverse = buildInverseCauchy(field, m);
|
|
402
|
+
return [mdsMat, mdsMatInverse];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function exponentForEven(mode: RescueMode, alpha: bigint, alphaInverse: bigint): bigint {
|
|
406
|
+
switch (mode.kind) {
|
|
407
|
+
case 'cipher': { return alphaInverse; }
|
|
408
|
+
case 'hash': { return alpha; }
|
|
409
|
+
default: return 0n;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function exponentForOdd(mode: RescueMode, alpha: bigint, alphaInverse: bigint): bigint {
|
|
414
|
+
switch (mode.kind) {
|
|
415
|
+
case 'cipher': { return alpha; }
|
|
416
|
+
case 'hash': { return alphaInverse; }
|
|
417
|
+
default: return 0n;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Core Rescue permutation function implementing the cryptographic primitive.
|
|
423
|
+
* Apply alternating rounds of exponentiation and MDS matrix multiplication with round keys.
|
|
424
|
+
* The permutation alternates between using alpha and alphaInverse as exponents based on round parity.
|
|
425
|
+
* This is the fundamental building block for both Rescue cipher and Rescue-Prime hash.
|
|
426
|
+
* @param mode - Rescue mode (cipher or hash) determining exponent selection.
|
|
427
|
+
* @param alpha - Prime exponent for even rounds.
|
|
428
|
+
* @param alphaInverse - Inverse exponent for odd rounds.
|
|
429
|
+
* @param mdsMat - Maximum Distance Separable matrix for diffusion.
|
|
430
|
+
* @param subkeys - Array of round key matrices.
|
|
431
|
+
* @param state - Initial state matrix to permute.
|
|
432
|
+
* @returns Array of all intermediate states during the permutation.
|
|
433
|
+
*/
|
|
434
|
+
function rescuePermutation(
|
|
435
|
+
mode: RescueMode,
|
|
436
|
+
alpha: bigint,
|
|
437
|
+
alphaInverse: bigint,
|
|
438
|
+
mdsMat: Matrix,
|
|
439
|
+
subkeys: Matrix[],
|
|
440
|
+
state: Matrix,
|
|
441
|
+
): Matrix[] {
|
|
442
|
+
const exponentEven = exponentForEven(mode, alpha, alphaInverse);
|
|
443
|
+
const exponentOdd = exponentForOdd(mode, alpha, alphaInverse);
|
|
444
|
+
const states = [state.add(subkeys[0])];
|
|
445
|
+
for (let r = 0; r < subkeys.length - 1; ++r) {
|
|
446
|
+
let s = states[r];
|
|
447
|
+
if (r % 2 === 0) {
|
|
448
|
+
s = s.pow(exponentEven);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
s = s.pow(exponentOdd);
|
|
452
|
+
}
|
|
453
|
+
states.push(mdsMat.matMul(s).add(subkeys[r + 1]));
|
|
454
|
+
}
|
|
455
|
+
return states;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function rescuePermutationInverse(
|
|
459
|
+
mode: RescueMode,
|
|
460
|
+
alpha: bigint,
|
|
461
|
+
alphaInverse: bigint,
|
|
462
|
+
mdsMatInverse: Matrix,
|
|
463
|
+
subkeys: Matrix[],
|
|
464
|
+
state: Matrix,
|
|
465
|
+
): Matrix[] {
|
|
466
|
+
const exponentEven = exponentForEven(mode, alpha, alphaInverse);
|
|
467
|
+
const exponentOdd = exponentForOdd(mode, alpha, alphaInverse);
|
|
468
|
+
// the initial state will need to be removed afterwards
|
|
469
|
+
const states = [state];
|
|
470
|
+
for (let r = 0; r < subkeys.length - 1; ++r) {
|
|
471
|
+
let s = states[r];
|
|
472
|
+
s = mdsMatInverse.matMul(s.sub(subkeys[subkeys.length - 1 - r]));
|
|
473
|
+
if (r % 2 === 0) {
|
|
474
|
+
s = s.pow(exponentEven);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
s = s.pow(exponentOdd);
|
|
478
|
+
}
|
|
479
|
+
states.push(s);
|
|
480
|
+
}
|
|
481
|
+
states.push(states[states.length - 1].sub(subkeys[0]));
|
|
482
|
+
states.shift();
|
|
483
|
+
return states;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export function toVec(data: bigint[]): bigint[][] {
|
|
487
|
+
const dataVec = [];
|
|
488
|
+
for (let i = 0; i < data.length; ++i) {
|
|
489
|
+
dataVec.push([data[i]]);
|
|
490
|
+
}
|
|
491
|
+
return dataVec;
|
|
492
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RescueDesc,
|
|
3
|
+
FpField,
|
|
4
|
+
} from './rescueDesc.js';
|
|
5
|
+
import { Matrix } from '../matrix.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The Rescue-Prime hash function, as described in https://eprint.iacr.org/2020/1143.pdf, offering 256 bits
|
|
9
|
+
* of security against collision, preimage and second-preimage attacks for any field of size at least 102 bits.
|
|
10
|
+
* We use the sponge construction with fixed rate = 7 and capacity = 5 (i.e., m = 12), and truncate the
|
|
11
|
+
* output to 5 field elements.
|
|
12
|
+
*/
|
|
13
|
+
export class RescuePrimeHash {
|
|
14
|
+
desc: RescueDesc;
|
|
15
|
+
|
|
16
|
+
rate: number;
|
|
17
|
+
|
|
18
|
+
digestLength: number;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Construct a RescuePrimeHash instance with rate = 7 and capacity = 5.
|
|
22
|
+
*/
|
|
23
|
+
constructor(field: FpField) {
|
|
24
|
+
this.desc = new RescueDesc(
|
|
25
|
+
field,
|
|
26
|
+
{ kind: 'hash', m: 12, capacity: 5 },
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
this.rate = 7;
|
|
30
|
+
this.digestLength = 5;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// This is Algorithm 1 from https://eprint.iacr.org/2020/1143.pdf, though with the padding (see Algorithm 2).
|
|
34
|
+
// The hash is truncated to digestLength elements.
|
|
35
|
+
// According to Section 2.2, this offers min(log2(CURVE25519_BASE_FIELD.ORDER) / 2 * min(digestLength, capacity), s)
|
|
36
|
+
// bits of security against collision, preimage and second-preimage attacks.
|
|
37
|
+
// The security level is thus of the order of 256 bits for any field of size at least 102 bits.
|
|
38
|
+
// The rate and capacity are chosen to achieve minimal number of rounds 8.
|
|
39
|
+
/**
|
|
40
|
+
* Compute the Rescue-Prime hash of a message, with padding as described in Algorithm 2 of the paper.
|
|
41
|
+
* @param message - Input message as an array of bigints.
|
|
42
|
+
* @returns Hash output as an array of bigints (length = digestLength).
|
|
43
|
+
*/
|
|
44
|
+
digest(message: bigint[]): bigint[] {
|
|
45
|
+
// Create a copy and pad message to avoid mutating input parameter
|
|
46
|
+
const paddedMessage = [...message, 1n];
|
|
47
|
+
while (paddedMessage.length % this.rate !== 0) {
|
|
48
|
+
paddedMessage.push(0n);
|
|
49
|
+
}
|
|
50
|
+
const zeros = [];
|
|
51
|
+
for (let i = 0; i < this.desc.m; ++i) {
|
|
52
|
+
zeros.push([0n]);
|
|
53
|
+
}
|
|
54
|
+
let state = new Matrix(this.desc.field, zeros);
|
|
55
|
+
for (let r = 0; r < paddedMessage.length / this.rate; ++r) {
|
|
56
|
+
const data = [];
|
|
57
|
+
for (let i = 0; i < this.rate; ++i) {
|
|
58
|
+
data[i] = [paddedMessage[r * this.rate + i]];
|
|
59
|
+
}
|
|
60
|
+
for (let i = this.rate; i < this.desc.m; ++i) {
|
|
61
|
+
data[i] = [0n];
|
|
62
|
+
}
|
|
63
|
+
const s = new Matrix(this.desc.field, data);
|
|
64
|
+
state = this.desc.permute(state.add(s, true));
|
|
65
|
+
}
|
|
66
|
+
const res = [];
|
|
67
|
+
for (let i = 0; i < this.digestLength; ++i) {
|
|
68
|
+
res.push(state.data[i][0]);
|
|
69
|
+
}
|
|
70
|
+
return res;
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/ctUtils.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a bigint to an array of bits (least significant to most significant, in 2's complement representation).
|
|
3
|
+
* @param x - Bigint to convert.
|
|
4
|
+
* @param binSize - Number of bits to use in the representation.
|
|
5
|
+
* @returns Array of booleans representing the bits of x.
|
|
6
|
+
*/
|
|
7
|
+
function toBinLE(x: bigint, binSize: bigint): boolean[] {
|
|
8
|
+
const res: boolean[] = [];
|
|
9
|
+
for (let i = 0; i < binSize; ++i) {
|
|
10
|
+
res.push(ctSignBit(x, BigInt(i)));
|
|
11
|
+
}
|
|
12
|
+
return res;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convert an array of bits (least significant to most significant, in 2's complement representation) to a bigint.
|
|
17
|
+
* @param xBin - Array of bits to convert.
|
|
18
|
+
* @returns Bigint represented by the bit array.
|
|
19
|
+
*/
|
|
20
|
+
function fromBinLE(xBin: boolean[]): bigint {
|
|
21
|
+
let res = 0n;
|
|
22
|
+
for (let i = 0; i < xBin.length - 1; ++i) {
|
|
23
|
+
res |= BigInt(xBin[i]) << BigInt(i);
|
|
24
|
+
}
|
|
25
|
+
return res - (BigInt(xBin[xBin.length - 1]) << BigInt(xBin.length - 1));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Binary adder between x and y (assumes xBin and yBin are of the same length and large enough to represent the sum).
|
|
30
|
+
* @param xBin - First operand as a bit array.
|
|
31
|
+
* @param yBin - Second operand as a bit array.
|
|
32
|
+
* @param carryIn - Initial carry-in value.
|
|
33
|
+
* @param binSize - Number of bits to use in the operation.
|
|
34
|
+
* @returns Sum as a bit array.
|
|
35
|
+
*/
|
|
36
|
+
function adder(xBin: boolean[], yBin: boolean[], carryIn: boolean, binSize: bigint): boolean[] {
|
|
37
|
+
const res: boolean[] = [];
|
|
38
|
+
let carry: boolean = carryIn;
|
|
39
|
+
for (let i = 0; i < binSize; ++i) {
|
|
40
|
+
// res[i] = xBin[i] XOR yBin[i] XOR carry
|
|
41
|
+
const yXorCarry = yBin[i] !== carry;
|
|
42
|
+
res.push(xBin[i] !== yXorCarry);
|
|
43
|
+
// newCarry = (xBin[i] AND yBin[i]) XOR (xBin[i] AND carry) XOR (yBin[i] AND carry)
|
|
44
|
+
// = (yBin[i] XOR carry) ? xBin[i] : yBin[i]
|
|
45
|
+
const newCarry = yBin[i] !== (yXorCarry && (xBin[i] !== yBin[i]));
|
|
46
|
+
carry = newCarry;
|
|
47
|
+
}
|
|
48
|
+
return res;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Constant-time addition of two bigints, using 2's complement representation.
|
|
53
|
+
* @param x - First operand.
|
|
54
|
+
* @param y - Second operand.
|
|
55
|
+
* @param binSize - Number of bits to use in the operation.
|
|
56
|
+
* @returns Sum as a bigint.
|
|
57
|
+
*/
|
|
58
|
+
export function ctAdd(x: bigint, y: bigint, binSize: bigint): bigint {
|
|
59
|
+
const resBin = adder(toBinLE(x, binSize), toBinLE(y, binSize), false, binSize);
|
|
60
|
+
return fromBinLE(resBin);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Constant-time subtraction of two bigints, using 2's complement representation.
|
|
65
|
+
* @param x - First operand.
|
|
66
|
+
* @param y - Second operand.
|
|
67
|
+
* @param binSize - Number of bits to use in the operation.
|
|
68
|
+
* @returns Difference as a bigint.
|
|
69
|
+
*/
|
|
70
|
+
export function ctSub(x: bigint, y: bigint, binSize: bigint): bigint {
|
|
71
|
+
const yBin = toBinLE(y, binSize);
|
|
72
|
+
const yBinNot: boolean[] = [];
|
|
73
|
+
for (let i = 0; i < binSize; ++i) {
|
|
74
|
+
yBinNot.push(yBin[i] === false);
|
|
75
|
+
}
|
|
76
|
+
const resBin = adder(toBinLE(x, binSize), yBinNot, true, binSize);
|
|
77
|
+
return fromBinLE(resBin);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Return the sign bit of a bigint in constant time.
|
|
82
|
+
* @param x - Bigint to check.
|
|
83
|
+
* @param binSize - Bit position to check (typically the highest bit).
|
|
84
|
+
* @returns True if the sign bit is set, false otherwise.
|
|
85
|
+
*/
|
|
86
|
+
export function ctSignBit(x: bigint, binSize: bigint): boolean {
|
|
87
|
+
return ((x >> binSize) & 1n) === 1n;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Constant-time less-than comparison for two bigints.
|
|
92
|
+
* @param x - First operand.
|
|
93
|
+
* @param y - Second operand.
|
|
94
|
+
* @param binSize - Number of bits to use in the operation.
|
|
95
|
+
* @returns True if x < y, false otherwise.
|
|
96
|
+
*/
|
|
97
|
+
export function ctLt(x: bigint, y: bigint, binSize: bigint): boolean {
|
|
98
|
+
return ctSignBit(ctSub(x, y, binSize), binSize);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Constant-time select between two bigints based on a boolean condition.
|
|
103
|
+
* @param b - Condition; if true, select x, otherwise select y.
|
|
104
|
+
* @param x - Value to select if b is true.
|
|
105
|
+
* @param y - Value to select if b is false.
|
|
106
|
+
* @param binSize - Number of bits to use in the operation.
|
|
107
|
+
* @returns Selected bigint.
|
|
108
|
+
*/
|
|
109
|
+
export function ctSelect(b: boolean, x: bigint, y: bigint, binSize: bigint): bigint {
|
|
110
|
+
return ctAdd(y, BigInt(b) * (ctSub(x, y, binSize)), binSize);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if a bigint fits in the range -2^binSize <= x < 2^binSize.
|
|
115
|
+
* Not constant-time for arbitrary x, but is constant-time for all inputs for which the function returns true.
|
|
116
|
+
* If you assert your inputs satisfy verifyBinSize(x, binSize), you need not care about the non constant-timeness of this function.
|
|
117
|
+
* @param x - Bigint to check.
|
|
118
|
+
* @param binSize - Number of bits to use in the check.
|
|
119
|
+
* @returns True if x fits in the range, false otherwise.
|
|
120
|
+
*/
|
|
121
|
+
export function verifyBinSize(x: bigint, binSize: bigint): boolean {
|
|
122
|
+
const bin = (x >> binSize).toString(2);
|
|
123
|
+
return bin === '0' || bin === '-1';
|
|
124
|
+
}
|