ox 0.14.25 → 0.14.27

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.
@@ -2133,3 +2133,336 @@ describe('toTuple', () => {
2133
2133
  ])
2134
2134
  })
2135
2135
  })
2136
+
2137
+ describe('witness (TIP-1053)', () => {
2138
+ const witness =
2139
+ '0x1111111111111111111111111111111111111111111111111111111111111111' as const
2140
+ const witness_other =
2141
+ '0x2222222222222222222222222222222222222222222222222222222222222222' as const
2142
+
2143
+ test('from: preserves witness', () => {
2144
+ const authorization = KeyAuthorization.from({
2145
+ address,
2146
+ chainId: 1n,
2147
+ type: 'secp256k1',
2148
+ witness,
2149
+ })
2150
+ expect(authorization.witness).toBe(witness)
2151
+ })
2152
+
2153
+ test('from: throws on non-32-byte witness', () => {
2154
+ expect(() =>
2155
+ KeyAuthorization.from({
2156
+ address,
2157
+ chainId: 1n,
2158
+ type: 'secp256k1',
2159
+ witness: '0xdeadbeef',
2160
+ }),
2161
+ ).toThrowErrorMatchingInlineSnapshot(
2162
+ `[KeyAuthorization.InvalidWitnessSizeError: Witness \`0xdeadbeef\` must be exactly 32 bytes (got 4 bytes).]`,
2163
+ )
2164
+ })
2165
+
2166
+ test('toTuple: appends witness as trailing field with defaulted earlier fields', () => {
2167
+ const authorization = KeyAuthorization.from({
2168
+ address,
2169
+ chainId: 1n,
2170
+ type: 'secp256k1',
2171
+ witness,
2172
+ })
2173
+ const [authTuple] = KeyAuthorization.toTuple(authorization)
2174
+ expect(authTuple).toEqual([
2175
+ '0x1', // chainId
2176
+ '0x', // keyType (secp256k1)
2177
+ address,
2178
+ '0x', // expiry default (never expires)
2179
+ '0x', // limits absent (RLP null placeholder)
2180
+ '0x', // scopes absent (RLP null placeholder)
2181
+ witness,
2182
+ ])
2183
+ })
2184
+
2185
+ test('toTuple: omits witness when absent (byte-equivalent to pre-TIP-1053)', () => {
2186
+ const withoutWitness = KeyAuthorization.from({
2187
+ address,
2188
+ chainId: 1n,
2189
+ type: 'secp256k1',
2190
+ })
2191
+ const [authTuple] = KeyAuthorization.toTuple(withoutWitness)
2192
+ expect((authTuple as unknown as unknown[]).length).toBe(3)
2193
+ })
2194
+
2195
+ test('serialize/deserialize: roundtrip with witness only', () => {
2196
+ const authorization = KeyAuthorization.from({
2197
+ address,
2198
+ chainId: 1n,
2199
+ type: 'secp256k1',
2200
+ witness,
2201
+ })
2202
+ const serialized = KeyAuthorization.serialize(authorization)
2203
+ const restored = KeyAuthorization.deserialize(serialized)
2204
+ expect(restored.witness).toBe(witness)
2205
+ expect(restored.expiry).toBeUndefined()
2206
+ expect(restored.limits).toBeUndefined()
2207
+ expect(restored.scopes).toBeUndefined()
2208
+ })
2209
+
2210
+ test('serialize/deserialize: roundtrip with witness + expiry + limits + scopes', () => {
2211
+ const authorization = KeyAuthorization.from({
2212
+ address,
2213
+ chainId: 1n,
2214
+ expiry,
2215
+ type: 'secp256k1',
2216
+ limits: [{ token, limit: Value.from('10', 6) }],
2217
+ scopes: [
2218
+ {
2219
+ address: token,
2220
+ selector: '0xa9059cbb',
2221
+ recipients: ['0x1111111111111111111111111111111111111111'],
2222
+ },
2223
+ ],
2224
+ witness,
2225
+ })
2226
+ const serialized = KeyAuthorization.serialize(authorization)
2227
+ const restored = KeyAuthorization.deserialize(serialized)
2228
+ expect(restored.witness).toBe(witness)
2229
+ expect(restored.expiry).toBe(expiry)
2230
+ expect(restored.limits?.[0]?.limit).toBe(10000000n)
2231
+ expect(restored.scopes?.[0]?.selector).toBe('0xa9059cbb')
2232
+ })
2233
+
2234
+ test('toRpc/fromRpc: roundtrip with witness', () => {
2235
+ const authorization = KeyAuthorization.from(
2236
+ {
2237
+ address,
2238
+ chainId: 1n,
2239
+ type: 'secp256k1',
2240
+ witness,
2241
+ },
2242
+ { signature: SignatureEnvelope.from(signature_secp256k1) },
2243
+ )
2244
+ const rpc = KeyAuthorization.toRpc(authorization)
2245
+ expect(rpc.witness).toBe(witness)
2246
+ const restored = KeyAuthorization.fromRpc(rpc)
2247
+ expect(restored.witness).toBe(witness)
2248
+ })
2249
+
2250
+ test('hash: changes when witness changes', () => {
2251
+ const a = KeyAuthorization.from({
2252
+ address,
2253
+ chainId: 1n,
2254
+ type: 'secp256k1',
2255
+ witness,
2256
+ })
2257
+ const b = KeyAuthorization.from({
2258
+ address,
2259
+ chainId: 1n,
2260
+ type: 'secp256k1',
2261
+ witness: witness_other,
2262
+ })
2263
+ expect(KeyAuthorization.hash(a)).not.toBe(KeyAuthorization.hash(b))
2264
+ })
2265
+
2266
+ test('hash: witness-less encoding matches pre-TIP-1053 hash', () => {
2267
+ const before = KeyAuthorization.from({
2268
+ address,
2269
+ chainId: 1n,
2270
+ type: 'secp256k1',
2271
+ })
2272
+ // Re-create the same auth (no witness field). Hash must be identical.
2273
+ const again = KeyAuthorization.from({
2274
+ address,
2275
+ chainId: 1n,
2276
+ type: 'secp256k1',
2277
+ })
2278
+ expect(KeyAuthorization.hash(before)).toBe(KeyAuthorization.hash(again))
2279
+ })
2280
+
2281
+ test('fromTuple: extracts trailing witness', () => {
2282
+ const restored = KeyAuthorization.fromTuple([
2283
+ ['0x01', '0x', address, '0x', [], [], witness],
2284
+ ])
2285
+ expect(restored.witness).toBe(witness)
2286
+ })
2287
+
2288
+ test('fromTuple: throws on non-32-byte witness', () => {
2289
+ expect(() =>
2290
+ KeyAuthorization.fromTuple([
2291
+ ['0x01', '0x', address, '0x', [], [], '0xdeadbeef'],
2292
+ ]),
2293
+ ).toThrowErrorMatchingInlineSnapshot(
2294
+ `[KeyAuthorization.InvalidWitnessSizeError: Witness \`0xdeadbeef\` must be exactly 32 bytes (got 4 bytes).]`,
2295
+ )
2296
+ })
2297
+
2298
+ test('signing flow: signature verifies against witness-bearing hash', () => {
2299
+ const authorization = KeyAuthorization.from({
2300
+ address,
2301
+ chainId: 1n,
2302
+ type: 'secp256k1',
2303
+ witness,
2304
+ })
2305
+ const payload = KeyAuthorization.getSignPayload(authorization)
2306
+ const sig = Secp256k1.sign({
2307
+ payload,
2308
+ privateKey: privateKey_secp256k1,
2309
+ })
2310
+ const signed = KeyAuthorization.from(authorization, {
2311
+ signature: SignatureEnvelope.from(sig),
2312
+ })
2313
+ const serialized = KeyAuthorization.serialize(signed)
2314
+ const restored = KeyAuthorization.deserialize(serialized)
2315
+ expect(restored.witness).toBe(witness)
2316
+ // The hash of the restored payload matches what was signed.
2317
+ expect(KeyAuthorization.hash(restored)).toBe(payload)
2318
+ })
2319
+ })
2320
+
2321
+ describe('admin keys (TIP-1049)', () => {
2322
+ const account = '0x1111111111111111111111111111111111111111' as const
2323
+
2324
+ test('from: preserves isAdmin and account', () => {
2325
+ const authorization = KeyAuthorization.from({
2326
+ address,
2327
+ account,
2328
+ chainId: 1n,
2329
+ isAdmin: true,
2330
+ type: 'secp256k1',
2331
+ })
2332
+ expect(authorization.isAdmin).toBe(true)
2333
+ expect(authorization.account).toBe(account)
2334
+ })
2335
+
2336
+ test('toTuple: emits isAdmin + account together', () => {
2337
+ const authorization = KeyAuthorization.from({
2338
+ address,
2339
+ account,
2340
+ chainId: 1n,
2341
+ isAdmin: true,
2342
+ type: 'secp256k1',
2343
+ })
2344
+ const [authTuple] = KeyAuthorization.toTuple(authorization)
2345
+ expect(authTuple).toEqual([
2346
+ '0x1',
2347
+ '0x',
2348
+ address,
2349
+ '0x', // expiry placeholder
2350
+ '0x', // limits placeholder
2351
+ '0x', // scopes placeholder
2352
+ '0x', // witness placeholder
2353
+ '0x01', // isAdmin = true
2354
+ account,
2355
+ ])
2356
+ })
2357
+
2358
+ test('toTuple: omits both when neither is set (byte-equivalent to pre-TIP-1049)', () => {
2359
+ const authorization = KeyAuthorization.from({
2360
+ address,
2361
+ chainId: 1n,
2362
+ type: 'secp256k1',
2363
+ })
2364
+ const [authTuple] = KeyAuthorization.toTuple(authorization)
2365
+ expect((authTuple as unknown as unknown[]).length).toBe(3)
2366
+ })
2367
+
2368
+ test('fromTuple: drops orphan isAdmin without account', () => {
2369
+ const restored = KeyAuthorization.fromTuple([
2370
+ ['0x01', '0x', address, '0x', [], [], '0x', '0x01'],
2371
+ ])
2372
+ expect(restored.isAdmin).toBeUndefined()
2373
+ expect(restored.account).toBeUndefined()
2374
+ })
2375
+
2376
+ test('fromTuple: drops orphan account without isAdmin', () => {
2377
+ const restored = KeyAuthorization.fromTuple([
2378
+ ['0x01', '0x', address, '0x', [], [], '0x', '0x', account],
2379
+ ])
2380
+ expect(restored.isAdmin).toBeUndefined()
2381
+ expect(restored.account).toBeUndefined()
2382
+ })
2383
+
2384
+ test('fromTuple: extracts isAdmin + account together', () => {
2385
+ const restored = KeyAuthorization.fromTuple([
2386
+ ['0x01', '0x', address, '0x', [], [], '0x', '0x01', account],
2387
+ ])
2388
+ expect(restored.isAdmin).toBe(true)
2389
+ expect(restored.account).toBe(account)
2390
+ })
2391
+
2392
+ test('fromTuple: throws on invalid admin marker', () => {
2393
+ expect(() =>
2394
+ KeyAuthorization.fromTuple([
2395
+ ['0x01', '0x', address, '0x', [], [], '0x', '0x02'],
2396
+ ]),
2397
+ ).toThrowErrorMatchingInlineSnapshot(
2398
+ `[KeyAuthorization.InvalidAdminMarkerError: Admin marker \`0x02\` is invalid; expected \`0x01\` (TIP-1049).]`,
2399
+ )
2400
+ })
2401
+
2402
+ test('serialize/deserialize: roundtrip with isAdmin + account', () => {
2403
+ const authorization = KeyAuthorization.from({
2404
+ address,
2405
+ account,
2406
+ chainId: 1n,
2407
+ isAdmin: true,
2408
+ type: 'secp256k1',
2409
+ })
2410
+ const serialized = KeyAuthorization.serialize(authorization)
2411
+ const restored = KeyAuthorization.deserialize(serialized)
2412
+ expect(restored.isAdmin).toBe(true)
2413
+ expect(restored.account).toBe(account)
2414
+ })
2415
+
2416
+ test('toRpc/fromRpc: roundtrip with isAdmin + account', () => {
2417
+ const authorization = KeyAuthorization.from(
2418
+ {
2419
+ address,
2420
+ account,
2421
+ chainId: 1n,
2422
+ isAdmin: true,
2423
+ type: 'secp256k1',
2424
+ },
2425
+ { signature: SignatureEnvelope.from(signature_secp256k1) },
2426
+ )
2427
+ const rpc = KeyAuthorization.toRpc(authorization)
2428
+ expect(rpc.isAdmin).toBe(true)
2429
+ expect(rpc.account).toBe(account)
2430
+ const restored = KeyAuthorization.fromRpc(rpc)
2431
+ expect(restored.isAdmin).toBe(true)
2432
+ expect(restored.account).toBe(account)
2433
+ })
2434
+
2435
+ test('hash: changes when admin pair is added', () => {
2436
+ const plain = KeyAuthorization.from({
2437
+ address,
2438
+ chainId: 1n,
2439
+ type: 'secp256k1',
2440
+ })
2441
+ const admin = KeyAuthorization.from({
2442
+ address,
2443
+ account,
2444
+ chainId: 1n,
2445
+ isAdmin: true,
2446
+ type: 'secp256k1',
2447
+ })
2448
+ expect(KeyAuthorization.hash(plain)).not.toBe(KeyAuthorization.hash(admin))
2449
+ })
2450
+
2451
+ test('hash: changes when account in admin pair changes', () => {
2452
+ const a = KeyAuthorization.from({
2453
+ address,
2454
+ account,
2455
+ chainId: 1n,
2456
+ isAdmin: true,
2457
+ type: 'secp256k1',
2458
+ })
2459
+ const b = KeyAuthorization.from({
2460
+ address,
2461
+ account: '0x2222222222222222222222222222222222222222',
2462
+ chainId: 1n,
2463
+ isAdmin: true,
2464
+ type: 'secp256k1',
2465
+ })
2466
+ expect(KeyAuthorization.hash(a)).not.toBe(KeyAuthorization.hash(b))
2467
+ })
2468
+ })