nansen-cli 1.6.0 → 1.7.0

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/src/crypto.js ADDED
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Shared cryptographic primitives for EVM transaction signing.
3
+ * Exports keccak256, secp256k1 ECDSA signing, RLP encoding.
4
+ * Zero external dependencies — uses Node.js built-in crypto only.
5
+ */
6
+
7
+ import crypto from "crypto";
8
+
9
+ // ============= Keccak-256 =============
10
+
11
+ // Keccak-256 (NOT SHA3-256; Ethereum uses original Keccak with 0x01 padding).
12
+ // Uses a flat 25-element state array (lanes indexed as state[x + 5*y])
13
+ // with BigInt64 arithmetic.
14
+
15
+ const RC = [
16
+ 0x0000000000000001n, 0x0000000000008082n, 0x800000000000808an, 0x8000000080008000n,
17
+ 0x000000000000808bn, 0x0000000080000001n, 0x8000000080008081n, 0x8000000000008009n,
18
+ 0x000000000000008an, 0x0000000000000088n, 0x0000000080008009n, 0x000000008000000an,
19
+ 0x000000008000808bn, 0x800000000000008bn, 0x8000000000008089n, 0x8000000000008003n,
20
+ 0x8000000000008002n, 0x8000000000000080n, 0x000000000000800an, 0x800000008000000an,
21
+ 0x8000000080008081n, 0x8000000000008080n, 0x0000000080000001n, 0x8000000080008008n,
22
+ ];
23
+
24
+ const ROT = [
25
+ 0, 1, 62, 28, 27,
26
+ 36, 44, 6, 55, 20,
27
+ 3, 10, 43, 25, 39,
28
+ 41, 45, 15, 21, 8,
29
+ 18, 2, 61, 56, 14,
30
+ ];
31
+
32
+ const M = 0xffffffffffffffffn;
33
+
34
+ function rot64(v, r) {
35
+ return r === 0 ? v : ((v << BigInt(r)) | (v >> BigInt(64 - r))) & M;
36
+ }
37
+
38
+ function keccakF(s) {
39
+ for (let round = 0; round < 24; round++) {
40
+ const c0 = s[0] ^ s[5] ^ s[10] ^ s[15] ^ s[20];
41
+ const c1 = s[1] ^ s[6] ^ s[11] ^ s[16] ^ s[21];
42
+ const c2 = s[2] ^ s[7] ^ s[12] ^ s[17] ^ s[22];
43
+ const c3 = s[3] ^ s[8] ^ s[13] ^ s[18] ^ s[23];
44
+ const c4 = s[4] ^ s[9] ^ s[14] ^ s[19] ^ s[24];
45
+ const d0 = (c4 ^ rot64(c1, 1)) & M;
46
+ const d1 = (c0 ^ rot64(c2, 1)) & M;
47
+ const d2 = (c1 ^ rot64(c3, 1)) & M;
48
+ const d3 = (c2 ^ rot64(c4, 1)) & M;
49
+ const d4 = (c3 ^ rot64(c0, 1)) & M;
50
+ for (let y = 0; y < 25; y += 5) {
51
+ s[y] = (s[y] ^ d0) & M;
52
+ s[y + 1] = (s[y + 1] ^ d1) & M;
53
+ s[y + 2] = (s[y + 2] ^ d2) & M;
54
+ s[y + 3] = (s[y + 3] ^ d3) & M;
55
+ s[y + 4] = (s[y + 4] ^ d4) & M;
56
+ }
57
+ const t = new Array(25);
58
+ for (let x = 0; x < 5; x++) {
59
+ for (let y = 0; y < 5; y++) {
60
+ const src = x + 5 * y;
61
+ const dst = y + 5 * ((2 * x + 3 * y) % 5);
62
+ t[dst] = rot64(s[src], ROT[src]);
63
+ }
64
+ }
65
+ for (let y = 0; y < 25; y += 5) {
66
+ const t0 = t[y], t1 = t[y+1], t2 = t[y+2], t3 = t[y+3], t4 = t[y+4];
67
+ s[y] = (t0 ^ ((~t1 & M) & t2)) & M;
68
+ s[y+1] = (t1 ^ ((~t2 & M) & t3)) & M;
69
+ s[y+2] = (t2 ^ ((~t3 & M) & t4)) & M;
70
+ s[y+3] = (t3 ^ ((~t4 & M) & t0)) & M;
71
+ s[y+4] = (t4 ^ ((~t0 & M) & t1)) & M;
72
+ }
73
+ s[0] = (s[0] ^ RC[round]) & M;
74
+ }
75
+ }
76
+
77
+ export function keccak256(input) {
78
+ const rate = 136;
79
+ const s = new Array(25).fill(0n);
80
+ const blocks = Math.max(1, Math.ceil((input.length + 1) / rate));
81
+ const padded = Buffer.alloc(blocks * rate);
82
+ input.copy(padded);
83
+ padded[input.length] ^= 0x01;
84
+ padded[padded.length - 1] ^= 0x80;
85
+ for (let off = 0; off < padded.length; off += rate) {
86
+ for (let i = 0; i < 17; i++) {
87
+ s[i] ^= padded.readBigUInt64LE(off + i * 8);
88
+ }
89
+ keccakF(s);
90
+ }
91
+ const out = Buffer.alloc(32);
92
+ for (let i = 0; i < 4; i++) {
93
+ out.writeBigUInt64LE(s[i] & M, i * 8);
94
+ }
95
+ return out;
96
+ }
97
+
98
+ // ============= secp256k1 ECDSA =============
99
+
100
+ const P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2Fn;
101
+ const N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n;
102
+ const Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798n;
103
+ const Gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8n;
104
+
105
+ function modInv(a, m) {
106
+ let [old_r, r] = [((a % m) + m) % m, m];
107
+ let [old_s, s] = [1n, 0n];
108
+ while (r !== 0n) {
109
+ const q = old_r / r;
110
+ [old_r, r] = [r, old_r - q * r];
111
+ [old_s, s] = [s, old_s - q * s];
112
+ }
113
+ return ((old_s % m) + m) % m;
114
+ }
115
+
116
+ function ptAdd(x1, y1, x2, y2) {
117
+ if (x1 === null) return [x2, y2];
118
+ if (x2 === null) return [x1, y1];
119
+ if (x1 === x2 && y1 === y2) {
120
+ const lam = (3n * x1 * x1 * modInv(2n * y1, P)) % P;
121
+ const x3 = ((lam * lam - 2n * x1) % P + P) % P;
122
+ return [x3, ((lam * (x1 - x3) - y1) % P + P) % P];
123
+ }
124
+ if (x1 === x2) return [null, null];
125
+ const lam = (((y2 - y1) % P + P) * modInv(((x2 - x1) % P + P) % P, P)) % P;
126
+ const x3 = ((lam * lam - x1 - x2) % P + P) % P;
127
+ return [x3, ((lam * (x1 - x3) - y1) % P + P) % P];
128
+ }
129
+
130
+ function ptMul(k, x, y) {
131
+ let [rx, ry] = [null, null];
132
+ let [qx, qy] = [x, y];
133
+ while (k > 0n) {
134
+ if (k & 1n) [rx, ry] = ptAdd(rx, ry, qx, qy);
135
+ [qx, qy] = ptAdd(qx, qy, qx, qy);
136
+ k >>= 1n;
137
+ }
138
+ return [rx, ry];
139
+ }
140
+
141
+ function rfc6979k(privBuf, hash) {
142
+ let v = Buffer.alloc(32, 0x01);
143
+ let k = Buffer.alloc(32, 0x00);
144
+ k = crypto.createHmac('sha256', k).update(Buffer.concat([v, Buffer.from([0x00]), privBuf, hash])).digest();
145
+ v = crypto.createHmac('sha256', k).update(v).digest();
146
+ k = crypto.createHmac('sha256', k).update(Buffer.concat([v, Buffer.from([0x01]), privBuf, hash])).digest();
147
+ v = crypto.createHmac('sha256', k).update(v).digest();
148
+ while (true) {
149
+ v = crypto.createHmac('sha256', k).update(v).digest();
150
+ const candidate = BigInt('0x' + v.toString('hex'));
151
+ if (candidate >= 1n && candidate < N) return candidate;
152
+ k = crypto.createHmac('sha256', k).update(Buffer.concat([v, Buffer.from([0x00])])).digest();
153
+ v = crypto.createHmac('sha256', k).update(v).digest();
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Sign a 32-byte hash with secp256k1 ECDSA.
159
+ * Uses RFC 6979 deterministic k and low-S normalization (EIP-2).
160
+ * Returns { r, s, v } where v is the recovery ID (0 or 1).
161
+ */
162
+ export function signSecp256k1(hash, privateKey) {
163
+ const z = BigInt('0x' + hash.toString('hex'));
164
+ const d = BigInt('0x' + privateKey.toString('hex'));
165
+ const k = rfc6979k(privateKey, hash);
166
+ const [rx, ry] = ptMul(k, Gx, Gy);
167
+ const r = rx % N;
168
+ if (r === 0n) throw new Error('Invalid signature: r=0');
169
+ let s = (modInv(k, N) * ((z + r * d) % N)) % N;
170
+ if (s === 0n) throw new Error('Invalid signature: s=0');
171
+ let v = (ry % 2n === 0n) ? 0 : 1;
172
+ if (s > N >> 1n) { s = N - s; v ^= 1; }
173
+ return {
174
+ r: Buffer.from(r.toString(16).padStart(64, '0'), 'hex'),
175
+ s: Buffer.from(s.toString(16).padStart(64, '0'), 'hex'),
176
+ v,
177
+ };
178
+ }
179
+
180
+ // ============= RLP Encoding =============
181
+
182
+ export function bigIntToMinBuf(n) {
183
+ if (n === 0n) return Buffer.alloc(0);
184
+ const hex = n.toString(16);
185
+ return Buffer.from(hex.length % 2 ? '0' + hex : hex, 'hex');
186
+ }
187
+
188
+ export function rlpEncode(input) {
189
+ if (Array.isArray(input)) {
190
+ const encoded = input.map(rlpEncode);
191
+ const payload = Buffer.concat(encoded);
192
+ if (payload.length < 56) {
193
+ return Buffer.concat([Buffer.from([0xc0 + payload.length]), payload]);
194
+ }
195
+ const lenBytes = bigIntToMinBuf(BigInt(payload.length));
196
+ return Buffer.concat([Buffer.from([0xf7 + lenBytes.length]), lenBytes, payload]);
197
+ }
198
+
199
+ let data;
200
+ if (Buffer.isBuffer(input)) {
201
+ data = input;
202
+ } else {
203
+ let hex = (typeof input === 'string' ? input : '').replace(/^0x/, '');
204
+ hex = hex.replace(/^0+/, '');
205
+ if (hex === '' || hex.length === 0) return Buffer.from([0x80]);
206
+ if (hex.length % 2) hex = '0' + hex;
207
+ data = Buffer.from(hex, 'hex');
208
+ }
209
+
210
+ if (data.length === 0) return Buffer.from([0x80]);
211
+ if (data.length === 1 && data[0] < 0x80) return data;
212
+ if (data.length < 56) return Buffer.concat([Buffer.from([0x80 + data.length]), data]);
213
+ const lenBytes = bigIntToMinBuf(BigInt(data.length));
214
+ return Buffer.concat([Buffer.from([0xb7 + lenBytes.length]), lenBytes, data]);
215
+ }