ox 1.0.0-beta.11 → 1.0.0-beta.13
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/dist/core/internal/rpcSchemas/eth.d.ts +16 -0
- package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.d.ts +37 -85
- package/dist/tempo/MultisigConfig.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.js +39 -83
- package/dist/tempo/MultisigConfig.js.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +32 -30
- package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.js +56 -51
- package/dist/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/tempo/index.d.ts +2 -2
- package/dist/tempo/index.js +2 -2
- package/dist/zod/RpcSchema.d.ts +1 -0
- package/dist/zod/RpcSchema.d.ts.map +1 -1
- package/dist/zod/internal/rpcSchemas/Eth.d.ts +1 -0
- package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
- package/dist/zod/internal/rpcSchemas/Eth.js +5 -0
- package/dist/zod/internal/rpcSchemas/Eth.js.map +1 -1
- package/dist/zod/tempo/AuthorizationTempo.d.ts +0 -20
- package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.d.ts +0 -11
- package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +0 -12
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.d.ts +0 -6
- package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.js +0 -2
- package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +0 -24
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.d.ts +0 -21
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts +0 -9
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +0 -3
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/internal/rpcSchemas/eth.ts +16 -0
- package/src/tempo/MultisigConfig.test.ts +54 -37
- package/src/tempo/MultisigConfig.ts +59 -134
- package/src/tempo/SignatureEnvelope.test.ts +118 -17
- package/src/tempo/SignatureEnvelope.ts +77 -72
- package/src/tempo/e2e.test.ts +221 -1
- package/src/tempo/index.ts +2 -2
- package/src/version.ts +1 -1
- package/src/zod/internal/rpcSchemas/Eth.ts +6 -0
- package/src/zod/tempo/SignatureEnvelope.ts +0 -2
- package/src/zod/tempo/_test/SignatureEnvelope.test.ts +0 -2
package/src/tempo/e2e.test.ts
CHANGED
|
@@ -3028,7 +3028,7 @@ describe.skip('behavior: multisig (TIP-1061)', () => {
|
|
|
3028
3028
|
|
|
3029
3029
|
const genesisConfig = MultisigConfig.from({
|
|
3030
3030
|
// A fresh random salt yields a distinct account each run, exercising the
|
|
3031
|
-
// salt-inclusive
|
|
3031
|
+
// salt-inclusive account derivation against the node.
|
|
3032
3032
|
salt: Hex.random(32),
|
|
3033
3033
|
threshold,
|
|
3034
3034
|
owners: ownerKeys.map((key) => ({
|
|
@@ -3204,4 +3204,224 @@ describe.skip('behavior: multisig (TIP-1061)', () => {
|
|
|
3204
3204
|
}),
|
|
3205
3205
|
).rejects.toThrow()
|
|
3206
3206
|
})
|
|
3207
|
+
|
|
3208
|
+
test('behavior: keychain access key (authorize via AccountKeychain + spend)', async () => {
|
|
3209
|
+
const { account, genesisConfig, ownerKeys } = setup({
|
|
3210
|
+
count: 2,
|
|
3211
|
+
threshold: 2,
|
|
3212
|
+
})
|
|
3213
|
+
|
|
3214
|
+
await fundAddress(client, { address: account })
|
|
3215
|
+
|
|
3216
|
+
// Fresh secp256k1 access key for the multisig account.
|
|
3217
|
+
const access = (() => {
|
|
3218
|
+
const privateKey = Secp256k1.randomPrivateKey()
|
|
3219
|
+
const address = Address.fromPublicKey(
|
|
3220
|
+
Secp256k1.getPublicKey({ privateKey }),
|
|
3221
|
+
)
|
|
3222
|
+
return { address, privateKey } as const
|
|
3223
|
+
})()
|
|
3224
|
+
|
|
3225
|
+
// Bootstrap the multisig (nonce 0).
|
|
3226
|
+
const bootstrap = TxEnvelopeTempo.from({
|
|
3227
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
3228
|
+
chainId,
|
|
3229
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
3230
|
+
nonce: 0n,
|
|
3231
|
+
gas: 2_000_000n,
|
|
3232
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
3233
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
3234
|
+
})
|
|
3235
|
+
|
|
3236
|
+
const bootstrap_signed = TxEnvelopeTempo.serialize(bootstrap, {
|
|
3237
|
+
signature: SignatureEnvelope.from({
|
|
3238
|
+
genesisConfig,
|
|
3239
|
+
init: true,
|
|
3240
|
+
signatures: approve({
|
|
3241
|
+
genesisConfig,
|
|
3242
|
+
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
3243
|
+
signers: ownerKeys,
|
|
3244
|
+
}),
|
|
3245
|
+
}),
|
|
3246
|
+
})
|
|
3247
|
+
|
|
3248
|
+
const bootstrap_receipt = (await client
|
|
3249
|
+
.request({
|
|
3250
|
+
method: 'eth_sendRawTransactionSync',
|
|
3251
|
+
params: [bootstrap_signed],
|
|
3252
|
+
})
|
|
3253
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
3254
|
+
expect(bootstrap_receipt.status).toBe('success')
|
|
3255
|
+
|
|
3256
|
+
// Authorize the access key (nonce 1): multisig transactions cannot carry
|
|
3257
|
+
// a TIP-1049 `keyAuthorization`, so the multisig account calls the
|
|
3258
|
+
// AccountKeychain precompile's `authorizeKey` directly.
|
|
3259
|
+
const authorizeKey = AbiFunction.from(
|
|
3260
|
+
'function authorizeKey(address keyId, uint8 signatureType, (uint64 expiry, bool enforceLimits, (address token, uint256 amount, uint64 period)[] limits, bool allowAnyCalls, (address target, (bytes4 selector, address[] recipients)[] selectorRules)[] allowedCalls) config)',
|
|
3261
|
+
)
|
|
3262
|
+
|
|
3263
|
+
const authorize = TxEnvelopeTempo.from({
|
|
3264
|
+
calls: [
|
|
3265
|
+
{
|
|
3266
|
+
to: '0xaaaaaaaa00000000000000000000000000000000',
|
|
3267
|
+
data: AbiFunction.encodeData(authorizeKey, [
|
|
3268
|
+
access.address,
|
|
3269
|
+
0, // secp256k1
|
|
3270
|
+
{
|
|
3271
|
+
expiry: 18446744073709551615n, // u64 max (never expires)
|
|
3272
|
+
enforceLimits: false,
|
|
3273
|
+
limits: [],
|
|
3274
|
+
allowAnyCalls: true,
|
|
3275
|
+
allowedCalls: [],
|
|
3276
|
+
},
|
|
3277
|
+
]),
|
|
3278
|
+
},
|
|
3279
|
+
],
|
|
3280
|
+
chainId,
|
|
3281
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
3282
|
+
nonce: 1n,
|
|
3283
|
+
gas: 2_000_000n,
|
|
3284
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
3285
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
3286
|
+
})
|
|
3287
|
+
|
|
3288
|
+
const authorize_signed = TxEnvelopeTempo.serialize(authorize, {
|
|
3289
|
+
signature: SignatureEnvelope.from({
|
|
3290
|
+
genesisConfig,
|
|
3291
|
+
signatures: approve({
|
|
3292
|
+
genesisConfig,
|
|
3293
|
+
payload: TxEnvelopeTempo.getSignPayload(authorize),
|
|
3294
|
+
signers: ownerKeys,
|
|
3295
|
+
}),
|
|
3296
|
+
}),
|
|
3297
|
+
})
|
|
3298
|
+
|
|
3299
|
+
const authorize_receipt = (await client
|
|
3300
|
+
.request({
|
|
3301
|
+
method: 'eth_sendRawTransactionSync',
|
|
3302
|
+
params: [authorize_signed],
|
|
3303
|
+
})
|
|
3304
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
3305
|
+
expect(authorize_receipt.status).toBe('success')
|
|
3306
|
+
expect(authorize_receipt.from).toBe(account)
|
|
3307
|
+
|
|
3308
|
+
// Spend with the access key (nonce 2): outer keychain signature with the
|
|
3309
|
+
// multisig account as `userAddress` — no owner approvals involved.
|
|
3310
|
+
const nonce = await getTransactionCount(client, {
|
|
3311
|
+
address: account,
|
|
3312
|
+
blockTag: 'pending',
|
|
3313
|
+
})
|
|
3314
|
+
|
|
3315
|
+
const spend = TxEnvelopeTempo.from({
|
|
3316
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
3317
|
+
chainId,
|
|
3318
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
3319
|
+
nonce: BigInt(nonce),
|
|
3320
|
+
gas: 2_000_000n,
|
|
3321
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
3322
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
3323
|
+
})
|
|
3324
|
+
|
|
3325
|
+
const spend_signature = Secp256k1.sign({
|
|
3326
|
+
payload: TxEnvelopeTempo.getSignPayload(spend, { from: account }),
|
|
3327
|
+
privateKey: access.privateKey,
|
|
3328
|
+
})
|
|
3329
|
+
|
|
3330
|
+
const spend_signed = TxEnvelopeTempo.serialize(spend, {
|
|
3331
|
+
signature: SignatureEnvelope.from({
|
|
3332
|
+
userAddress: account,
|
|
3333
|
+
inner: SignatureEnvelope.from(spend_signature),
|
|
3334
|
+
type: 'keychain',
|
|
3335
|
+
}),
|
|
3336
|
+
})
|
|
3337
|
+
|
|
3338
|
+
const spend_receipt = (await client
|
|
3339
|
+
.request({
|
|
3340
|
+
method: 'eth_sendRawTransactionSync',
|
|
3341
|
+
params: [spend_signed],
|
|
3342
|
+
})
|
|
3343
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
3344
|
+
expect(spend_receipt.status).toBe('success')
|
|
3345
|
+
expect(spend_receipt.from).toBe(account)
|
|
3346
|
+
|
|
3347
|
+
{
|
|
3348
|
+
const response = await client
|
|
3349
|
+
.request({
|
|
3350
|
+
method: 'eth_getTransactionByHash',
|
|
3351
|
+
params: [spend_receipt.transactionHash],
|
|
3352
|
+
})
|
|
3353
|
+
.then((tx) => Transaction.fromRpc(tx as any))
|
|
3354
|
+
if (!response) throw new Error()
|
|
3355
|
+
expect(response.from).toBe(account)
|
|
3356
|
+
expect(response.signature?.type).toBe('keychain')
|
|
3357
|
+
expect(
|
|
3358
|
+
(response.signature as SignatureEnvelope.Keychain | undefined)
|
|
3359
|
+
?.userAddress,
|
|
3360
|
+
).toBe(account)
|
|
3361
|
+
}
|
|
3362
|
+
})
|
|
3363
|
+
|
|
3364
|
+
test('behavior: rejects `keyAuthorization` on multisig transactions', async () => {
|
|
3365
|
+
const { account, genesisConfig, ownerKeys } = setup({
|
|
3366
|
+
count: 1,
|
|
3367
|
+
threshold: 1,
|
|
3368
|
+
})
|
|
3369
|
+
|
|
3370
|
+
await fundAddress(client, { address: account })
|
|
3371
|
+
|
|
3372
|
+
const access = (() => {
|
|
3373
|
+
const privateKey = Secp256k1.randomPrivateKey()
|
|
3374
|
+
const address = Address.fromPublicKey(
|
|
3375
|
+
Secp256k1.getPublicKey({ privateKey }),
|
|
3376
|
+
)
|
|
3377
|
+
return { address, privateKey } as const
|
|
3378
|
+
})()
|
|
3379
|
+
|
|
3380
|
+
// TIP-1049 key authorizations are only valid on primitive/keychain
|
|
3381
|
+
// transactions; multisig accounts must call the AccountKeychain
|
|
3382
|
+
// precompile instead.
|
|
3383
|
+
const keyAuth = KeyAuthorization.from({
|
|
3384
|
+
address: access.address,
|
|
3385
|
+
chainId: BigInt(chainId),
|
|
3386
|
+
type: 'secp256k1',
|
|
3387
|
+
})
|
|
3388
|
+
const keyAuth_signed = KeyAuthorization.from(keyAuth, {
|
|
3389
|
+
signature: SignatureEnvelope.from(
|
|
3390
|
+
Secp256k1.sign({
|
|
3391
|
+
payload: KeyAuthorization.getSignPayload(keyAuth),
|
|
3392
|
+
privateKey: ownerKeys[0]!.privateKey,
|
|
3393
|
+
}),
|
|
3394
|
+
),
|
|
3395
|
+
})
|
|
3396
|
+
|
|
3397
|
+
const bootstrap = TxEnvelopeTempo.from({
|
|
3398
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
3399
|
+
chainId,
|
|
3400
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
3401
|
+
keyAuthorization: keyAuth_signed,
|
|
3402
|
+
nonce: 0n,
|
|
3403
|
+
gas: 2_000_000n,
|
|
3404
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
3405
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
3406
|
+
})
|
|
3407
|
+
|
|
3408
|
+
const serialized_signed = TxEnvelopeTempo.serialize(bootstrap, {
|
|
3409
|
+
signature: SignatureEnvelope.from({
|
|
3410
|
+
genesisConfig,
|
|
3411
|
+
init: true,
|
|
3412
|
+
signatures: approve({
|
|
3413
|
+
genesisConfig,
|
|
3414
|
+
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
3415
|
+
signers: ownerKeys,
|
|
3416
|
+
}),
|
|
3417
|
+
}),
|
|
3418
|
+
})
|
|
3419
|
+
|
|
3420
|
+
await expect(
|
|
3421
|
+
client.request({
|
|
3422
|
+
method: 'eth_sendRawTransactionSync',
|
|
3423
|
+
params: [serialized_signed],
|
|
3424
|
+
}),
|
|
3425
|
+
).rejects.toThrow()
|
|
3426
|
+
})
|
|
3207
3427
|
})
|
package/src/tempo/index.ts
CHANGED
|
@@ -122,8 +122,8 @@ export * as KeyAuthorization from './KeyAuthorization.js'
|
|
|
122
122
|
/**
|
|
123
123
|
* Native multisig account utilities (TIP-1061).
|
|
124
124
|
*
|
|
125
|
-
* Derives stable multisig account addresses
|
|
126
|
-
*
|
|
125
|
+
* Derives stable multisig account addresses from a weighted owner
|
|
126
|
+
* configuration, and computes the owner approval digest that owners sign.
|
|
127
127
|
*
|
|
128
128
|
* [TIP-1061](https://tips.sh/1061)
|
|
129
129
|
*
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @internal */
|
|
2
|
-
export const version = '1.0.0-beta.
|
|
2
|
+
export const version = '1.0.0-beta.13'
|
|
@@ -181,6 +181,12 @@ export const eth_getProof = from({
|
|
|
181
181
|
returns: z_AccountProof.AccountProof,
|
|
182
182
|
})
|
|
183
183
|
|
|
184
|
+
export const eth_getRawTransactionByHash = from({
|
|
185
|
+
method: 'eth_getRawTransactionByHash',
|
|
186
|
+
params: z.tuple([z_Hex.Hex]),
|
|
187
|
+
returns: z.nullable(z_Hex.Hex),
|
|
188
|
+
})
|
|
189
|
+
|
|
184
190
|
export const eth_getStorageAt = from({
|
|
185
191
|
method: 'eth_getStorageAt',
|
|
186
192
|
params: z.tuple([z_Address.Address, z_Hex.Hex, BlockNumberOrTagOrIdentifier]),
|
|
@@ -83,7 +83,6 @@ export const KeychainRpc = z.object({
|
|
|
83
83
|
/** RPC native multisig signature envelope schema. */
|
|
84
84
|
export const MultisigRpc = z.object({
|
|
85
85
|
account: z_Address.Address,
|
|
86
|
-
configId: z_Hex.Hex,
|
|
87
86
|
init: z.optional(z_MultisigConfig.Config),
|
|
88
87
|
// Owner approvals are raw serialized signatures (node `Vec<Bytes>`).
|
|
89
88
|
signatures: z.readonly(z.array(z_Hex.Hex)),
|
|
@@ -140,7 +139,6 @@ export const Keychain = z.object({
|
|
|
140
139
|
/** Native multisig signature envelope schema. */
|
|
141
140
|
export const Multisig = z.object({
|
|
142
141
|
account: z_Address.Address,
|
|
143
|
-
genesisConfigId: z_Hex.Hex,
|
|
144
142
|
init: z.optional(z_MultisigConfig.Config),
|
|
145
143
|
// `signatures` is recursive; type the getter concretely to break the cycle.
|
|
146
144
|
signatures: z.lazy(
|
|
@@ -21,8 +21,6 @@ const p256 = {
|
|
|
21
21
|
|
|
22
22
|
const multisig = {
|
|
23
23
|
account: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c',
|
|
24
|
-
configId:
|
|
25
|
-
'0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
26
24
|
signatures: [
|
|
27
25
|
core_SignatureEnvelope.serialize(core_SignatureEnvelope.fromRpc(secp256k1)),
|
|
28
26
|
],
|