ox 0.14.30 → 0.14.32
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/CHANGELOG.md +25 -0
- package/_cjs/core/AbiParameters.js +2 -1
- package/_cjs/core/AbiParameters.js.map +1 -1
- package/_cjs/core/internal/abiParameters.js +10 -2
- package/_cjs/core/internal/abiParameters.js.map +1 -1
- package/_cjs/tempo/EarnShares.js +66 -0
- package/_cjs/tempo/EarnShares.js.map +1 -0
- package/_cjs/tempo/KeyAuthorization.js +39 -10
- package/_cjs/tempo/KeyAuthorization.js.map +1 -1
- package/_cjs/tempo/MultisigConfig.js +12 -2
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +167 -46
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/tempo/index.js +2 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/core/AbiParameters.js +4 -1
- package/_esm/core/AbiParameters.js.map +1 -1
- package/_esm/core/internal/abiParameters.js +17 -2
- package/_esm/core/internal/abiParameters.js.map +1 -1
- package/_esm/tempo/EarnShares.js +167 -0
- package/_esm/tempo/EarnShares.js.map +1 -0
- package/_esm/tempo/KeyAuthorization.js +41 -9
- package/_esm/tempo/KeyAuthorization.js.map +1 -1
- package/_esm/tempo/MultisigConfig.js +16 -4
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +172 -55
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/index.js +22 -0
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/core/AbiParameters.d.ts.map +1 -1
- package/_types/core/internal/abiParameters.d.ts.map +1 -1
- package/_types/core/internal/rpcSchemas/eth.d.ts +16 -0
- package/_types/core/internal/rpcSchemas/eth.d.ts.map +1 -1
- package/_types/tempo/EarnShares.d.ts +176 -0
- package/_types/tempo/EarnShares.d.ts.map +1 -0
- package/_types/tempo/KeyAuthorization.d.ts +22 -16
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigConfig.d.ts +8 -4
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +33 -13
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +22 -0
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/core/AbiParameters.ts +3 -1
- package/core/internal/abiParameters.ts +17 -2
- package/core/internal/rpcSchemas/eth.ts +16 -0
- package/package.json +16 -1
- package/tempo/EarnShares/package.json +6 -0
- package/tempo/EarnShares.test.ts +122 -0
- package/tempo/EarnShares.ts +232 -0
- package/tempo/KeyAuthorization.test-d/package.json +6 -0
- package/tempo/KeyAuthorization.test-d.ts +62 -0
- package/tempo/KeyAuthorization.test.ts +132 -0
- package/tempo/KeyAuthorization.ts +76 -29
- package/tempo/MultisigConfig.test.ts +34 -2
- package/tempo/MultisigConfig.ts +18 -4
- package/tempo/SignatureEnvelope.test-d/package.json +6 -0
- package/tempo/SignatureEnvelope.test-d.ts +97 -0
- package/tempo/SignatureEnvelope.test.ts +227 -36
- package/tempo/SignatureEnvelope.ts +276 -85
- package/tempo/e2e.test.ts +0 -416
- package/tempo/index.ts +22 -0
- package/tempo/multisig.e2e.test.ts +582 -0
- package/version.ts +1 -1
|
@@ -53,6 +53,12 @@ const signature_webauthn = SignatureEnvelope.from({
|
|
|
53
53
|
},
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
+
const signature_multisig = SignatureEnvelope.from({
|
|
57
|
+
account: address,
|
|
58
|
+
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
59
|
+
type: 'multisig',
|
|
60
|
+
})
|
|
61
|
+
|
|
56
62
|
describe('from', () => {
|
|
57
63
|
test('default', () => {
|
|
58
64
|
const authorization = KeyAuthorization.from({
|
|
@@ -441,9 +447,63 @@ describe('from', () => {
|
|
|
441
447
|
}
|
|
442
448
|
`)
|
|
443
449
|
})
|
|
450
|
+
|
|
451
|
+
test('rejects a multisig signature', () => {
|
|
452
|
+
const authorization = KeyAuthorization.from({
|
|
453
|
+
address,
|
|
454
|
+
chainId: 1n,
|
|
455
|
+
type: 'secp256k1',
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
expect(() =>
|
|
459
|
+
KeyAuthorization.from(authorization, {
|
|
460
|
+
signature: signature_multisig as never,
|
|
461
|
+
}),
|
|
462
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
463
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
expect(() =>
|
|
467
|
+
KeyAuthorization.from(authorization, {
|
|
468
|
+
signature: SignatureEnvelope.serialize(signature_multisig),
|
|
469
|
+
}),
|
|
470
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
471
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
expect(() =>
|
|
475
|
+
KeyAuthorization.from({
|
|
476
|
+
...authorization,
|
|
477
|
+
signature: signature_multisig,
|
|
478
|
+
} as never),
|
|
479
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
480
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
|
|
481
|
+
)
|
|
482
|
+
})
|
|
444
483
|
})
|
|
445
484
|
|
|
446
485
|
describe('fromRpc', () => {
|
|
486
|
+
test('rejects a multisig signature', () => {
|
|
487
|
+
expect(() =>
|
|
488
|
+
KeyAuthorization.fromRpc({
|
|
489
|
+
chainId: '0x1',
|
|
490
|
+
expiry: null,
|
|
491
|
+
keyId: address,
|
|
492
|
+
keyType: 'secp256k1',
|
|
493
|
+
signature: {
|
|
494
|
+
account: address,
|
|
495
|
+
signatures: [
|
|
496
|
+
SignatureEnvelope.toRpc(
|
|
497
|
+
SignatureEnvelope.from(signature_secp256k1),
|
|
498
|
+
),
|
|
499
|
+
],
|
|
500
|
+
},
|
|
501
|
+
} as never),
|
|
502
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
503
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
|
|
504
|
+
)
|
|
505
|
+
})
|
|
506
|
+
|
|
447
507
|
test('secp256k1', () => {
|
|
448
508
|
const authorization = KeyAuthorization.fromRpc({
|
|
449
509
|
chainId: '0x1',
|
|
@@ -661,6 +721,17 @@ describe('fromRpc', () => {
|
|
|
661
721
|
})
|
|
662
722
|
|
|
663
723
|
describe('fromTuple', () => {
|
|
724
|
+
test('rejects a multisig signature', () => {
|
|
725
|
+
expect(() =>
|
|
726
|
+
KeyAuthorization.fromTuple([
|
|
727
|
+
['0x1', '0x', address],
|
|
728
|
+
SignatureEnvelope.serialize(signature_multisig),
|
|
729
|
+
]),
|
|
730
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
731
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
|
|
732
|
+
)
|
|
733
|
+
})
|
|
734
|
+
|
|
664
735
|
test('default', () => {
|
|
665
736
|
const authorization = KeyAuthorization.fromTuple([
|
|
666
737
|
[
|
|
@@ -998,6 +1069,19 @@ describe('getSignPayload', () => {
|
|
|
998
1069
|
})
|
|
999
1070
|
|
|
1000
1071
|
describe('deserialize', () => {
|
|
1072
|
+
test('rejects a multisig signature', () => {
|
|
1073
|
+
const serialized = Rlp.fromHex([
|
|
1074
|
+
['0x1', '0x', address],
|
|
1075
|
+
SignatureEnvelope.serialize(signature_multisig),
|
|
1076
|
+
])
|
|
1077
|
+
|
|
1078
|
+
expect(() =>
|
|
1079
|
+
KeyAuthorization.deserialize(serialized),
|
|
1080
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
1081
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
|
|
1082
|
+
)
|
|
1083
|
+
})
|
|
1084
|
+
|
|
1001
1085
|
test('default', () => {
|
|
1002
1086
|
const authorization = KeyAuthorization.from({
|
|
1003
1087
|
address,
|
|
@@ -1168,6 +1252,19 @@ describe('serialize', () => {
|
|
|
1168
1252
|
})
|
|
1169
1253
|
|
|
1170
1254
|
describe('toRpc', () => {
|
|
1255
|
+
test('rejects a multisig signature', () => {
|
|
1256
|
+
expect(() =>
|
|
1257
|
+
KeyAuthorization.toRpc({
|
|
1258
|
+
address,
|
|
1259
|
+
chainId: 1n,
|
|
1260
|
+
signature: signature_multisig,
|
|
1261
|
+
type: 'secp256k1',
|
|
1262
|
+
} as never),
|
|
1263
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
1264
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
|
|
1265
|
+
)
|
|
1266
|
+
})
|
|
1267
|
+
|
|
1171
1268
|
test('secp256k1', () => {
|
|
1172
1269
|
const authorization = KeyAuthorization.from({
|
|
1173
1270
|
address,
|
|
@@ -1425,6 +1522,19 @@ describe('toRpc', () => {
|
|
|
1425
1522
|
})
|
|
1426
1523
|
|
|
1427
1524
|
describe('toTuple', () => {
|
|
1525
|
+
test('rejects a multisig signature', () => {
|
|
1526
|
+
expect(() =>
|
|
1527
|
+
KeyAuthorization.toTuple({
|
|
1528
|
+
address,
|
|
1529
|
+
chainId: 1n,
|
|
1530
|
+
signature: signature_multisig,
|
|
1531
|
+
type: 'secp256k1',
|
|
1532
|
+
} as never),
|
|
1533
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
1534
|
+
`[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
|
|
1535
|
+
)
|
|
1536
|
+
})
|
|
1537
|
+
|
|
1428
1538
|
test('default', () => {
|
|
1429
1539
|
const authorization = KeyAuthorization.from({
|
|
1430
1540
|
address,
|
|
@@ -2432,6 +2542,28 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2432
2542
|
expect(restored.account).toBe(account)
|
|
2433
2543
|
})
|
|
2434
2544
|
|
|
2545
|
+
test('fromRpc: drops orphan isAdmin without account', () => {
|
|
2546
|
+
const authorization = KeyAuthorization.from(
|
|
2547
|
+
{ address, chainId: 1n, type: 'secp256k1' },
|
|
2548
|
+
{ signature: SignatureEnvelope.from(signature_secp256k1) },
|
|
2549
|
+
)
|
|
2550
|
+
const rpc = KeyAuthorization.toRpc(authorization)
|
|
2551
|
+
const restored = KeyAuthorization.fromRpc({ ...rpc, isAdmin: true })
|
|
2552
|
+
expect(restored.isAdmin).toBeUndefined()
|
|
2553
|
+
expect(restored.account).toBeUndefined()
|
|
2554
|
+
})
|
|
2555
|
+
|
|
2556
|
+
test('fromRpc: drops orphan account without isAdmin', () => {
|
|
2557
|
+
const authorization = KeyAuthorization.from(
|
|
2558
|
+
{ address, chainId: 1n, type: 'secp256k1' },
|
|
2559
|
+
{ signature: SignatureEnvelope.from(signature_secp256k1) },
|
|
2560
|
+
)
|
|
2561
|
+
const rpc = KeyAuthorization.toRpc(authorization)
|
|
2562
|
+
const restored = KeyAuthorization.fromRpc({ ...rpc, account })
|
|
2563
|
+
expect(restored.isAdmin).toBeUndefined()
|
|
2564
|
+
expect(restored.account).toBeUndefined()
|
|
2565
|
+
})
|
|
2566
|
+
|
|
2435
2567
|
test('hash: changes when admin pair is added', () => {
|
|
2436
2568
|
const plain = KeyAuthorization.from({
|
|
2437
2569
|
address,
|
|
@@ -3,7 +3,7 @@ import type * as Address from '../core/Address.js'
|
|
|
3
3
|
import type * as Errors from '../core/Errors.js'
|
|
4
4
|
import * as Hash from '../core/Hash.js'
|
|
5
5
|
import * as Hex from '../core/Hex.js'
|
|
6
|
-
import type { Compute, OneOf } from '../core/internal/types.js'
|
|
6
|
+
import type { Compute, OneOf, UnionPartialBy } from '../core/internal/types.js'
|
|
7
7
|
import * as Rlp from '../core/Rlp.js'
|
|
8
8
|
import * as SignatureEnvelope from './SignatureEnvelope.js'
|
|
9
9
|
import * as TempoAddress from './TempoAddress.js'
|
|
@@ -81,10 +81,10 @@ export type KeyAuthorization<
|
|
|
81
81
|
| {}
|
|
82
82
|
> &
|
|
83
83
|
(signed extends true
|
|
84
|
-
? { signature: SignatureEnvelope.
|
|
84
|
+
? { signature: SignatureEnvelope.Primitive<bigintType, numberType> }
|
|
85
85
|
: {
|
|
86
86
|
signature?:
|
|
87
|
-
| SignatureEnvelope.
|
|
87
|
+
| SignatureEnvelope.Primitive<bigintType, numberType>
|
|
88
88
|
| undefined
|
|
89
89
|
})
|
|
90
90
|
|
|
@@ -114,8 +114,8 @@ export type Rpc = {
|
|
|
114
114
|
keyType: SignatureEnvelope.Type
|
|
115
115
|
/** Token spending limits. */
|
|
116
116
|
limits?: readonly RpcTokenLimit[] | undefined
|
|
117
|
-
/**
|
|
118
|
-
signature: SignatureEnvelope.
|
|
117
|
+
/** Primitive signature authorizing this key. */
|
|
118
|
+
signature: SignatureEnvelope.PrimitiveRpc
|
|
119
119
|
/** Optional 32-byte witness (hex). */
|
|
120
120
|
witness?: Hex.Hex | null | undefined
|
|
121
121
|
}
|
|
@@ -146,6 +146,11 @@ export type Signed<
|
|
|
146
146
|
addressType = Address.Address,
|
|
147
147
|
> = KeyAuthorization<true, bigintType, numberType, addressType>
|
|
148
148
|
|
|
149
|
+
type PrimitiveSignatureValue =
|
|
150
|
+
| UnionPartialBy<SignatureEnvelope.Primitive, 'prehash' | 'type'>
|
|
151
|
+
| SignatureEnvelope.Secp256k1Flat
|
|
152
|
+
| SignatureEnvelope.Serialized
|
|
153
|
+
|
|
149
154
|
type BaseTuple = readonly [
|
|
150
155
|
chainId: Hex.Hex,
|
|
151
156
|
keyType: Hex.Hex,
|
|
@@ -403,7 +408,7 @@ export type TokenLimit<
|
|
|
403
408
|
*/
|
|
404
409
|
export function from<
|
|
405
410
|
const authorization extends Input | Rpc,
|
|
406
|
-
const signature extends
|
|
411
|
+
const signature extends PrimitiveSignatureValue | undefined = undefined,
|
|
407
412
|
>(
|
|
408
413
|
authorization: authorization | KeyAuthorization,
|
|
409
414
|
options: from.Options<signature> = {},
|
|
@@ -418,6 +423,7 @@ export function from<
|
|
|
418
423
|
}[]
|
|
419
424
|
}
|
|
420
425
|
if (auth.witness !== undefined) assertWitness(auth.witness)
|
|
426
|
+
if (auth.signature) assertSignature(auth.signature)
|
|
421
427
|
const resolved = {
|
|
422
428
|
...auth,
|
|
423
429
|
address: TempoAddress.resolve(auth.address as TempoAddress.Address),
|
|
@@ -446,41 +452,51 @@ export function from<
|
|
|
446
452
|
}
|
|
447
453
|
: {}),
|
|
448
454
|
}
|
|
449
|
-
if (options.signature)
|
|
455
|
+
if (options.signature) {
|
|
456
|
+
const signature = SignatureEnvelope.from(
|
|
457
|
+
options.signature,
|
|
458
|
+
) as SignatureEnvelope.SignatureEnvelope
|
|
459
|
+
assertSignature(signature)
|
|
450
460
|
return {
|
|
451
461
|
...resolved,
|
|
452
|
-
signature
|
|
462
|
+
signature,
|
|
453
463
|
} as never
|
|
464
|
+
}
|
|
454
465
|
return resolved as never
|
|
455
466
|
}
|
|
456
467
|
|
|
457
468
|
export declare namespace from {
|
|
458
469
|
type Options<
|
|
459
|
-
signature extends
|
|
460
|
-
|
|
|
470
|
+
signature extends PrimitiveSignatureValue | undefined =
|
|
471
|
+
| PrimitiveSignatureValue
|
|
461
472
|
| undefined,
|
|
462
473
|
> = {
|
|
463
|
-
/** The
|
|
464
|
-
signature?: signature | SignatureEnvelope.
|
|
474
|
+
/** The primitive signature to attach to the Key Authorization. */
|
|
475
|
+
signature?: signature | SignatureEnvelope.Primitive | undefined
|
|
465
476
|
}
|
|
466
477
|
|
|
467
478
|
type ReturnType<
|
|
468
479
|
authorization extends KeyAuthorization | Input | Rpc = KeyAuthorization,
|
|
469
|
-
signature extends
|
|
470
|
-
|
|
|
480
|
+
signature extends PrimitiveSignatureValue | undefined =
|
|
481
|
+
| PrimitiveSignatureValue
|
|
471
482
|
| undefined,
|
|
472
483
|
> = Compute<
|
|
473
484
|
authorization extends Rpc
|
|
474
485
|
? Signed
|
|
475
486
|
: TempoAddress.ResolveAddresses<
|
|
476
487
|
authorization &
|
|
477
|
-
(signature extends
|
|
478
|
-
? {
|
|
488
|
+
(signature extends PrimitiveSignatureValue
|
|
489
|
+
? {
|
|
490
|
+
signature: Extract<
|
|
491
|
+
SignatureEnvelope.from.ReturnValue<signature>,
|
|
492
|
+
SignatureEnvelope.Primitive
|
|
493
|
+
>
|
|
494
|
+
}
|
|
479
495
|
: {})
|
|
480
496
|
>
|
|
481
497
|
>
|
|
482
498
|
|
|
483
|
-
type ErrorType = Errors.GlobalErrorType
|
|
499
|
+
type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
|
|
484
500
|
}
|
|
485
501
|
|
|
486
502
|
/**
|
|
@@ -515,6 +531,7 @@ export function fromRpc(authorization: Rpc): Signed {
|
|
|
515
531
|
const isAdmin = authorization.isAdmin ?? undefined
|
|
516
532
|
const account = authorization.account ?? undefined
|
|
517
533
|
const signature = SignatureEnvelope.fromRpc(authorization.signature)
|
|
534
|
+
assertSignature(signature)
|
|
518
535
|
if (witness !== undefined) assertWitness(witness)
|
|
519
536
|
|
|
520
537
|
// Unflatten nested allowedCalls into flat scopes
|
|
@@ -534,6 +551,12 @@ export function fromRpc(authorization: Rpc): Signed {
|
|
|
534
551
|
})
|
|
535
552
|
: undefined
|
|
536
553
|
|
|
554
|
+
// TIP-1049 admin fields are paired: emit both or neither; orphan wire
|
|
555
|
+
// fields are dropped. Separate conditional spreads break `Signed`
|
|
556
|
+
// assignability without `exactOptionalPropertyTypes` (#290).
|
|
557
|
+
const adminPair =
|
|
558
|
+
account !== undefined && isAdmin ? { account, isAdmin: true as const } : {}
|
|
559
|
+
|
|
537
560
|
return {
|
|
538
561
|
address: keyId,
|
|
539
562
|
chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
|
|
@@ -549,13 +572,12 @@ export function fromRpc(authorization: Rpc): Signed {
|
|
|
549
572
|
signature,
|
|
550
573
|
type: keyType,
|
|
551
574
|
...(witness !== undefined ? { witness } : {}),
|
|
552
|
-
...
|
|
553
|
-
...(account !== undefined ? { account } : {}),
|
|
575
|
+
...adminPair,
|
|
554
576
|
}
|
|
555
577
|
}
|
|
556
578
|
|
|
557
579
|
export declare namespace fromRpc {
|
|
558
|
-
type ErrorType = Errors.GlobalErrorType
|
|
580
|
+
type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
|
|
559
581
|
}
|
|
560
582
|
|
|
561
583
|
/**
|
|
@@ -687,8 +709,11 @@ export function fromTuple<const tuple extends Tuple>(
|
|
|
687
709
|
...(witness !== undefined ? { witness } : {}),
|
|
688
710
|
...adminPair,
|
|
689
711
|
}
|
|
690
|
-
if (signatureSerialized)
|
|
691
|
-
|
|
712
|
+
if (signatureSerialized) {
|
|
713
|
+
const signature = SignatureEnvelope.deserialize(signatureSerialized)
|
|
714
|
+
assertSignature(signature)
|
|
715
|
+
args.signature = signature
|
|
716
|
+
}
|
|
692
717
|
return from(args) as never
|
|
693
718
|
}
|
|
694
719
|
|
|
@@ -697,7 +722,7 @@ export declare namespace fromTuple {
|
|
|
697
722
|
KeyAuthorization<authorization extends Tuple<true> ? true : false>
|
|
698
723
|
>
|
|
699
724
|
|
|
700
|
-
type ErrorType = Errors.GlobalErrorType
|
|
725
|
+
type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
|
|
701
726
|
}
|
|
702
727
|
|
|
703
728
|
/**
|
|
@@ -901,6 +926,7 @@ export function toRpc(authorization: Signed): Rpc {
|
|
|
901
926
|
isAdmin,
|
|
902
927
|
account,
|
|
903
928
|
} = authorization
|
|
929
|
+
assertSignature(signature)
|
|
904
930
|
if (witness !== undefined) assertWitness(witness)
|
|
905
931
|
|
|
906
932
|
// Group flat scopes by address into nested allowedCalls wire format
|
|
@@ -937,7 +963,9 @@ export function toRpc(authorization: Signed): Rpc {
|
|
|
937
963
|
limit: Hex.fromNumber(limit),
|
|
938
964
|
...(period ? { period: numberToHex(period) } : {}),
|
|
939
965
|
})),
|
|
940
|
-
signature: SignatureEnvelope.toRpc(
|
|
966
|
+
signature: SignatureEnvelope.toRpc(
|
|
967
|
+
signature,
|
|
968
|
+
) as SignatureEnvelope.PrimitiveRpc,
|
|
941
969
|
...(allowedCalls ? { allowedCalls } : {}),
|
|
942
970
|
...(witness !== undefined ? { witness } : {}),
|
|
943
971
|
...(isAdmin ? { isAdmin: true } : {}),
|
|
@@ -946,7 +974,7 @@ export function toRpc(authorization: Signed): Rpc {
|
|
|
946
974
|
}
|
|
947
975
|
|
|
948
976
|
export declare namespace toRpc {
|
|
949
|
-
type ErrorType = Errors.GlobalErrorType
|
|
977
|
+
type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
|
|
950
978
|
}
|
|
951
979
|
|
|
952
980
|
/**
|
|
@@ -994,9 +1022,11 @@ export function toTuple<const authorization extends KeyAuthorization>(
|
|
|
994
1022
|
account,
|
|
995
1023
|
} = authorization
|
|
996
1024
|
if (witness !== undefined) assertWitness(witness)
|
|
997
|
-
const signature =
|
|
998
|
-
|
|
999
|
-
|
|
1025
|
+
const signature = (() => {
|
|
1026
|
+
if (!authorization.signature) return undefined
|
|
1027
|
+
assertSignature(authorization.signature)
|
|
1028
|
+
return SignatureEnvelope.serialize(authorization.signature)
|
|
1029
|
+
})()
|
|
1000
1030
|
const type = (() => {
|
|
1001
1031
|
switch (authorization.type) {
|
|
1002
1032
|
case 'secp256k1':
|
|
@@ -1092,7 +1122,7 @@ export declare namespace toTuple {
|
|
|
1092
1122
|
type ReturnType<authorization extends KeyAuthorization = KeyAuthorization> =
|
|
1093
1123
|
Compute<Tuple<authorization extends KeyAuthorization<true> ? true : false>>
|
|
1094
1124
|
|
|
1095
|
-
type ErrorType = Errors.GlobalErrorType
|
|
1125
|
+
type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
|
|
1096
1126
|
}
|
|
1097
1127
|
|
|
1098
1128
|
function bigintToHex(value: bigint): Hex.Hex {
|
|
@@ -1130,6 +1160,13 @@ function assertWitness(witness: Hex.Hex): void {
|
|
|
1130
1160
|
if (Hex.size(witness) !== 32) throw new InvalidWitnessSizeError(witness)
|
|
1131
1161
|
}
|
|
1132
1162
|
|
|
1163
|
+
function assertSignature<bigintType, numberType>(
|
|
1164
|
+
signature: SignatureEnvelope.SignatureEnvelope<bigintType, numberType>,
|
|
1165
|
+
): asserts signature is SignatureEnvelope.Primitive<bigintType, numberType> {
|
|
1166
|
+
if (signature.type === 'keychain' || signature.type === 'multisig')
|
|
1167
|
+
throw new InvalidSignatureTypeError(signature.type)
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1133
1170
|
function isAbsent(value: unknown): boolean {
|
|
1134
1171
|
return value === undefined || value === '0x'
|
|
1135
1172
|
}
|
|
@@ -1153,3 +1190,13 @@ export class InvalidAdminMarkerError extends Error {
|
|
|
1153
1190
|
)
|
|
1154
1191
|
}
|
|
1155
1192
|
}
|
|
1193
|
+
|
|
1194
|
+
/** Thrown when a key authorization contains a non-primitive signature. */
|
|
1195
|
+
export class InvalidSignatureTypeError extends Error {
|
|
1196
|
+
override readonly name = 'KeyAuthorization.InvalidSignatureTypeError'
|
|
1197
|
+
constructor(type: SignatureEnvelope.SignatureEnvelope['type']) {
|
|
1198
|
+
super(
|
|
1199
|
+
`Signature type \`${type}\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.`,
|
|
1200
|
+
)
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
@@ -154,7 +154,12 @@ describe('assert / validate', () => {
|
|
|
154
154
|
owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
|
|
155
155
|
weight: 1,
|
|
156
156
|
}))
|
|
157
|
-
expect(
|
|
157
|
+
expect(
|
|
158
|
+
MultisigConfig.validate({
|
|
159
|
+
threshold: MultisigConfig.maxThreshold,
|
|
160
|
+
owners,
|
|
161
|
+
}),
|
|
162
|
+
).toBe(true)
|
|
158
163
|
})
|
|
159
164
|
|
|
160
165
|
test('too many owners', () => {
|
|
@@ -174,6 +179,24 @@ describe('assert / validate', () => {
|
|
|
174
179
|
).toBe(false)
|
|
175
180
|
})
|
|
176
181
|
|
|
182
|
+
test('threshold exceeds protocol maximum', () => {
|
|
183
|
+
expect(
|
|
184
|
+
MultisigConfig.validate({
|
|
185
|
+
threshold: MultisigConfig.maxThreshold + 1,
|
|
186
|
+
owners: [{ owner: owner1, weight: MultisigConfig.maxThreshold + 1 }],
|
|
187
|
+
}),
|
|
188
|
+
).toBe(false)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('fractional threshold', () => {
|
|
192
|
+
expect(
|
|
193
|
+
MultisigConfig.validate({
|
|
194
|
+
threshold: 1.5,
|
|
195
|
+
owners: [{ owner: owner1, weight: 2 }],
|
|
196
|
+
}),
|
|
197
|
+
).toBe(false)
|
|
198
|
+
})
|
|
199
|
+
|
|
177
200
|
test('threshold exceeds total weight', () => {
|
|
178
201
|
expect(
|
|
179
202
|
MultisigConfig.validate({
|
|
@@ -186,7 +209,7 @@ describe('assert / validate', () => {
|
|
|
186
209
|
test('total weight exceeds u8 max', () => {
|
|
187
210
|
expect(
|
|
188
211
|
MultisigConfig.validate({
|
|
189
|
-
threshold:
|
|
212
|
+
threshold: MultisigConfig.maxThreshold,
|
|
190
213
|
owners: [
|
|
191
214
|
{ owner: owner1, weight: 128 },
|
|
192
215
|
{ owner: owner2, weight: 128 },
|
|
@@ -204,6 +227,15 @@ describe('assert / validate', () => {
|
|
|
204
227
|
).toBe(false)
|
|
205
228
|
})
|
|
206
229
|
|
|
230
|
+
test('fractional owner weight', () => {
|
|
231
|
+
expect(
|
|
232
|
+
MultisigConfig.validate({
|
|
233
|
+
threshold: 1,
|
|
234
|
+
owners: [{ owner: owner1, weight: 1.5 }],
|
|
235
|
+
}),
|
|
236
|
+
).toBe(false)
|
|
237
|
+
})
|
|
238
|
+
|
|
207
239
|
test('zero owner address', () => {
|
|
208
240
|
expect(
|
|
209
241
|
MultisigConfig.validate({
|
package/tempo/MultisigConfig.ts
CHANGED
|
@@ -8,13 +8,19 @@ import type { Compute, OneOf } from '../core/internal/types.js'
|
|
|
8
8
|
/** Maximum number of owners allowed in a native multisig config. */
|
|
9
9
|
export const maxOwners = 255
|
|
10
10
|
|
|
11
|
+
/** Maximum threshold accepted by a native multisig config. */
|
|
12
|
+
export const maxThreshold = 8
|
|
13
|
+
|
|
14
|
+
/** Maximum number of owner approvals in a native multisig signature. */
|
|
15
|
+
export const maxSignatures = maxThreshold
|
|
16
|
+
|
|
11
17
|
/**
|
|
12
18
|
* Maximum number of native multisig signatures in one nested authorization
|
|
13
19
|
* path, including the top-level transaction signature.
|
|
14
20
|
*/
|
|
15
|
-
export const maxNestingDepth =
|
|
21
|
+
export const maxNestingDepth = 2
|
|
16
22
|
|
|
17
|
-
/** Maximum encoded byte length for one
|
|
23
|
+
/** Maximum encoded byte length for one owner approval. */
|
|
18
24
|
export const maxOwnerSignatureBytes = 2049
|
|
19
25
|
|
|
20
26
|
/** Tempo signature type byte for native multisig signatures. */
|
|
@@ -65,8 +71,8 @@ export type Tuple = readonly [
|
|
|
65
71
|
*
|
|
66
72
|
* Mirrors the Tempo `InitMultisig::validate` rules: owners non-empty and
|
|
67
73
|
* `<= maxOwners`, strictly ascending unique nonzero owner addresses, nonzero
|
|
68
|
-
* owner weights, `threshold
|
|
69
|
-
* `threshold <= total weight`.
|
|
74
|
+
* integer owner weights, integer `threshold` between `1` and `maxThreshold`,
|
|
75
|
+
* total weight `<= 255` (u8 max), and `threshold <= total weight`.
|
|
70
76
|
*
|
|
71
77
|
* @example
|
|
72
78
|
* ```ts twoslash
|
|
@@ -91,14 +97,22 @@ export function assert<numberType = number>(config: Config<numberType>): void {
|
|
|
91
97
|
throw new InvalidConfigError({ reason: 'owners cannot be empty' })
|
|
92
98
|
if (owners.length > maxOwners)
|
|
93
99
|
throw new InvalidConfigError({ reason: 'too many owners' })
|
|
100
|
+
if (!Number.isInteger(Number(threshold)))
|
|
101
|
+
throw new InvalidConfigError({ reason: 'threshold must be an integer' })
|
|
94
102
|
if (Number(threshold) < 1)
|
|
95
103
|
throw new InvalidConfigError({ reason: 'threshold cannot be zero' })
|
|
104
|
+
if (Number(threshold) > maxThreshold)
|
|
105
|
+
throw new InvalidConfigError({ reason: 'threshold exceeds max threshold' })
|
|
96
106
|
|
|
97
107
|
let totalWeight = 0
|
|
98
108
|
let previous: bigint | undefined
|
|
99
109
|
for (const owner of owners) {
|
|
100
110
|
if (!Address.validate(owner.owner) || Hex.toBigInt(owner.owner) === 0n)
|
|
101
111
|
throw new InvalidConfigError({ reason: 'owner cannot be zero' })
|
|
112
|
+
if (!Number.isInteger(Number(owner.weight)))
|
|
113
|
+
throw new InvalidConfigError({
|
|
114
|
+
reason: 'owner weight must be an integer',
|
|
115
|
+
})
|
|
102
116
|
if (Number(owner.weight) < 1)
|
|
103
117
|
throw new InvalidConfigError({ reason: 'owner weight cannot be zero' })
|
|
104
118
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vitest'
|
|
2
|
+
import * as SignatureEnvelope from './SignatureEnvelope.js'
|
|
3
|
+
|
|
4
|
+
const signatureRpc = {
|
|
5
|
+
r: '0x01',
|
|
6
|
+
s: '0x02',
|
|
7
|
+
type: 'secp256k1',
|
|
8
|
+
yParity: '0x0',
|
|
9
|
+
} as const satisfies SignatureEnvelope.SignatureEnvelopeRpc
|
|
10
|
+
|
|
11
|
+
const signature = {
|
|
12
|
+
signature: {
|
|
13
|
+
r: 1n,
|
|
14
|
+
s: 2n,
|
|
15
|
+
yParity: 0,
|
|
16
|
+
},
|
|
17
|
+
type: 'secp256k1',
|
|
18
|
+
} as const satisfies SignatureEnvelope.Secp256k1
|
|
19
|
+
|
|
20
|
+
test('toRpc preserves the signature type', () => {
|
|
21
|
+
expectTypeOf(
|
|
22
|
+
SignatureEnvelope.toRpc(signature),
|
|
23
|
+
).toEqualTypeOf<SignatureEnvelope.Secp256k1Rpc>()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('MultisigRpc uses static initialized and bootstrap shapes', () => {
|
|
27
|
+
const initialized = {
|
|
28
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
29
|
+
signatures: [signatureRpc],
|
|
30
|
+
} as const satisfies SignatureEnvelope.MultisigRpc
|
|
31
|
+
const bootstrap = {
|
|
32
|
+
init: {
|
|
33
|
+
owners: [
|
|
34
|
+
{
|
|
35
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
36
|
+
weight: 1,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
threshold: 1,
|
|
40
|
+
},
|
|
41
|
+
signatures: [signatureRpc],
|
|
42
|
+
} as const satisfies SignatureEnvelope.MultisigRpc
|
|
43
|
+
const bootstrapWithAccountUndefined: SignatureEnvelope.MultisigRpc = {
|
|
44
|
+
...bootstrap,
|
|
45
|
+
account: undefined,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
expectTypeOf(initialized.signatures).toMatchTypeOf<
|
|
49
|
+
readonly SignatureEnvelope.SignatureEnvelopeRpc[]
|
|
50
|
+
>()
|
|
51
|
+
expectTypeOf(bootstrap.signatures).toMatchTypeOf<
|
|
52
|
+
readonly SignatureEnvelope.SignatureEnvelopeRpc[]
|
|
53
|
+
>()
|
|
54
|
+
expectTypeOf(
|
|
55
|
+
bootstrapWithAccountUndefined,
|
|
56
|
+
).toMatchTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
57
|
+
expectTypeOf<
|
|
58
|
+
SignatureEnvelope.GetType<typeof bootstrap>
|
|
59
|
+
>().toEqualTypeOf<'multisig'>()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('MultisigRpc rejects legacy shapes', () => {
|
|
63
|
+
const combined = {
|
|
64
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
65
|
+
init: {
|
|
66
|
+
owners: [
|
|
67
|
+
{
|
|
68
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
69
|
+
weight: 1,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
threshold: 1,
|
|
73
|
+
},
|
|
74
|
+
signatures: [signatureRpc],
|
|
75
|
+
} as const
|
|
76
|
+
// @ts-expect-error Bootstrap RPC signatures omit `account`.
|
|
77
|
+
const combinedRpc: SignatureEnvelope.MultisigRpc = combined
|
|
78
|
+
|
|
79
|
+
const serialized = {
|
|
80
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
81
|
+
signatures: ['0x1234'],
|
|
82
|
+
} as const
|
|
83
|
+
// @ts-expect-error Owner approvals use structured RPC envelopes.
|
|
84
|
+
const serializedRpc: SignatureEnvelope.MultisigRpc = serialized
|
|
85
|
+
|
|
86
|
+
const tagged = {
|
|
87
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
88
|
+
signatures: [signatureRpc],
|
|
89
|
+
type: 'multisig',
|
|
90
|
+
} as const
|
|
91
|
+
// @ts-expect-error Multisig RPC signatures are untagged.
|
|
92
|
+
const taggedRpc: SignatureEnvelope.MultisigRpc = tagged
|
|
93
|
+
|
|
94
|
+
expectTypeOf(combinedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
95
|
+
expectTypeOf(serializedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
96
|
+
expectTypeOf(taggedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
97
|
+
})
|