ox 0.14.41 → 0.14.43
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/tempo/MultisigOperation.js +27 -0
- package/_cjs/tempo/MultisigOperation.js.map +1 -1
- package/_cjs/tempo/MultisigSimulation.js +115 -0
- package/_cjs/tempo/MultisigSimulation.js.map +1 -0
- package/_cjs/tempo/SignatureEnvelope.js +12 -21
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/tempo/TransactionRequest.js +7 -7
- package/_cjs/tempo/TransactionRequest.js.map +1 -1
- package/_cjs/tempo/index.js +2 -2
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/MultisigOperation.js +48 -0
- package/_esm/tempo/MultisigOperation.js.map +1 -1
- package/_esm/tempo/MultisigSimulation.js +145 -0
- package/_esm/tempo/MultisigSimulation.js.map +1 -0
- package/_esm/tempo/SignatureEnvelope.js +12 -21
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/TransactionRequest.js +7 -7
- package/_esm/tempo/TransactionRequest.js.map +1 -1
- package/_esm/tempo/index.js +4 -4
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +1 -1
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigOperation.d.ts +36 -0
- package/_types/tempo/MultisigOperation.d.ts.map +1 -1
- package/_types/tempo/MultisigSimulation.d.ts +132 -0
- package/_types/tempo/MultisigSimulation.d.ts.map +1 -0
- package/_types/tempo/RpcSchemaTempo.d.ts +10 -0
- package/_types/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +10 -34
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/TransactionRequest.d.ts +9 -9
- package/_types/tempo/TransactionRequest.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +4 -4
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/package.json +9 -9
- package/tempo/AuthorizationTempo.test.ts +4 -0
- package/tempo/KeyAuthorization.test-d.ts +1 -12
- package/tempo/KeyAuthorization.test.ts +34 -6
- package/tempo/KeyAuthorization.ts +3 -3
- package/tempo/MultisigOperation.test.ts +94 -0
- package/tempo/MultisigOperation.ts +83 -0
- package/tempo/MultisigSimulation/package.json +6 -0
- package/tempo/MultisigSimulation.test-d/package.json +6 -0
- package/tempo/MultisigSimulation.test-d.ts +33 -0
- package/tempo/MultisigSimulation.test.ts +284 -0
- package/tempo/MultisigSimulation.ts +266 -0
- package/tempo/RpcSchemaTempo.test-d.ts +14 -4
- package/tempo/RpcSchemaTempo.ts +9 -0
- package/tempo/SignatureEnvelope.test-d.ts +8 -44
- package/tempo/SignatureEnvelope.test.ts +92 -51
- package/tempo/SignatureEnvelope.ts +27 -61
- package/tempo/Transaction.test.ts +61 -0
- package/tempo/TransactionRequest.test-d.ts +9 -9
- package/tempo/TransactionRequest.test.ts +122 -49
- package/tempo/TransactionRequest.ts +20 -16
- package/tempo/index.ts +4 -4
- package/tempo/multisig.e2e.test.ts +51 -1
- package/version.ts +1 -1
- package/_cjs/tempo/MultisigWitness.js +0 -92
- package/_cjs/tempo/MultisigWitness.js.map +0 -1
- package/_esm/tempo/MultisigWitness.js +0 -123
- package/_esm/tempo/MultisigWitness.js.map +0 -1
- package/_types/tempo/MultisigWitness.d.ts +0 -106
- package/_types/tempo/MultisigWitness.d.ts.map +0 -1
- package/tempo/MultisigWitness/package.json +0 -6
- package/tempo/MultisigWitness.test-d/package.json +0 -6
- package/tempo/MultisigWitness.test-d.ts +0 -42
- package/tempo/MultisigWitness.test.ts +0 -289
- package/tempo/MultisigWitness.ts +0 -210
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import * as Address from '../core/Address.js'
|
|
2
|
+
import * as Errors from '../core/Errors.js'
|
|
3
|
+
import * as Hex from '../core/Hex.js'
|
|
4
|
+
import type { Compute } from '../core/internal/types.js'
|
|
5
|
+
import * as Rlp from '../core/Rlp.js'
|
|
6
|
+
import * as MultisigConfig from './MultisigConfig.js'
|
|
7
|
+
import type * as SignatureEnvelope from './SignatureEnvelope.js'
|
|
8
|
+
|
|
9
|
+
/** Native multisig owner approval used for RPC simulation. */
|
|
10
|
+
export type Approval = NestedApproval | PrimitiveApproval
|
|
11
|
+
|
|
12
|
+
/** JSON-RPC native multisig owner approval used for RPC simulation. */
|
|
13
|
+
export type ApprovalRpc = NestedApprovalRpc | PrimitiveApproval
|
|
14
|
+
|
|
15
|
+
/** Nested native multisig owner approval used for RPC simulation. */
|
|
16
|
+
export type NestedApproval = {
|
|
17
|
+
/** Depth-2 multisig simulation spec. */
|
|
18
|
+
spec: NestedSpec
|
|
19
|
+
/** Approval type. */
|
|
20
|
+
type: 'multisig'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** JSON-RPC nested native multisig owner approval used for RPC simulation. */
|
|
24
|
+
export type NestedApprovalRpc = {
|
|
25
|
+
/** Depth-2 multisig simulation spec. */
|
|
26
|
+
spec: NestedSpecRpc
|
|
27
|
+
/** Approval type. */
|
|
28
|
+
type: 'multisig'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Primitive approval in a depth-2 multisig simulation spec. */
|
|
32
|
+
export type NestedPrimitiveApproval = {
|
|
33
|
+
/** Optional signature-specific gas-estimation data. */
|
|
34
|
+
keyData?: Hex.Hex | undefined
|
|
35
|
+
/** Signature type to model. Omission uses a maximum-size WebAuthn signature. */
|
|
36
|
+
keyType?: SignatureEnvelope.Type | undefined
|
|
37
|
+
/** Configured owner address. */
|
|
38
|
+
owner: Address.Address
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Depth-2 native multisig spec used for RPC simulation. */
|
|
42
|
+
export type NestedSpec = {
|
|
43
|
+
/** Nested multisig account. */
|
|
44
|
+
account: Address.Address
|
|
45
|
+
/** Primitive owner approvals to model. */
|
|
46
|
+
approvals: readonly NestedPrimitiveApproval[]
|
|
47
|
+
/** Complete applicable configuration. */
|
|
48
|
+
config: MultisigConfig.Config
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** JSON-RPC depth-2 native multisig spec used for RPC simulation. */
|
|
52
|
+
export type NestedSpecRpc = {
|
|
53
|
+
/** Nested multisig account. */
|
|
54
|
+
account: Address.Address
|
|
55
|
+
/** Primitive owner approvals to model. */
|
|
56
|
+
approvals: readonly NestedPrimitiveApproval[]
|
|
57
|
+
/** Canonical RLP-encoded applicable configuration. */
|
|
58
|
+
config: Hex.Hex
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Primitive owner approval used for RPC simulation. */
|
|
62
|
+
export type PrimitiveApproval = {
|
|
63
|
+
/** Optional signature-specific gas-estimation data. */
|
|
64
|
+
keyData?: Hex.Hex | undefined
|
|
65
|
+
/** Signature type to model. Omission uses a maximum-size WebAuthn signature. */
|
|
66
|
+
keyType?: SignatureEnvelope.Type | undefined
|
|
67
|
+
/** Configured owner address. */
|
|
68
|
+
owner: Address.Address
|
|
69
|
+
/** Approval type. */
|
|
70
|
+
type: 'primitive'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** JSON-RPC representation of a native multisig simulation spec. */
|
|
74
|
+
export type Rpc = Compute<{
|
|
75
|
+
/** Account authorized by this spec. */
|
|
76
|
+
account: Address.Address
|
|
77
|
+
/** Owner approvals to model. */
|
|
78
|
+
approvals: readonly ApprovalRpc[]
|
|
79
|
+
/** Canonical RLP-encoded applicable configuration. */
|
|
80
|
+
config: Hex.Hex
|
|
81
|
+
}>
|
|
82
|
+
|
|
83
|
+
/** Native multisig spec used to construct an RPC simulation signature. */
|
|
84
|
+
export type Spec = Compute<{
|
|
85
|
+
/** Account authorized by this spec. */
|
|
86
|
+
account: Address.Address
|
|
87
|
+
/** Owner approvals to model. */
|
|
88
|
+
approvals: readonly Approval[]
|
|
89
|
+
/** Complete applicable configuration. */
|
|
90
|
+
config: MultisigConfig.Config
|
|
91
|
+
}>
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Converts a JSON-RPC multisig simulation spec to its domain representation.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts twoslash
|
|
98
|
+
* // @noErrors
|
|
99
|
+
* import { MultisigSimulation } from 'ox/tempo'
|
|
100
|
+
*
|
|
101
|
+
* const spec = MultisigSimulation.fromRpc(specRpc)
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @param spec - JSON-RPC multisig simulation spec.
|
|
105
|
+
* @returns The multisig simulation spec with decoded configurations.
|
|
106
|
+
*/
|
|
107
|
+
export function fromRpc(spec: Rpc): Spec {
|
|
108
|
+
const { account, approvals, config } = spec
|
|
109
|
+
assertApprovalCount(approvals)
|
|
110
|
+
return {
|
|
111
|
+
account,
|
|
112
|
+
approvals: approvals.map((approval) => {
|
|
113
|
+
if (approval.type === 'primitive') return approval
|
|
114
|
+
assertApprovalCount(approval.spec.approvals)
|
|
115
|
+
return {
|
|
116
|
+
spec: {
|
|
117
|
+
account: approval.spec.account,
|
|
118
|
+
approvals: approval.spec.approvals,
|
|
119
|
+
config: deserializeConfig(approval.spec.config),
|
|
120
|
+
},
|
|
121
|
+
type: 'multisig',
|
|
122
|
+
}
|
|
123
|
+
}),
|
|
124
|
+
config: deserializeConfig(config),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export declare namespace fromRpc {
|
|
129
|
+
/** Error type for `fromRpc`. */
|
|
130
|
+
export type ErrorType =
|
|
131
|
+
| Hex.isEqual.ErrorType
|
|
132
|
+
| InvalidSimulationError
|
|
133
|
+
| MultisigConfig.assert.ErrorType
|
|
134
|
+
| Rlp.fromHex.ErrorType
|
|
135
|
+
| Rlp.toHex.ErrorType
|
|
136
|
+
| Errors.GlobalErrorType
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Converts a multisig simulation spec to its JSON-RPC representation.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts twoslash
|
|
144
|
+
* // @noErrors
|
|
145
|
+
* import { MultisigSimulation } from 'ox/tempo'
|
|
146
|
+
*
|
|
147
|
+
* const specRpc = MultisigSimulation.toRpc(spec)
|
|
148
|
+
* ```
|
|
149
|
+
*
|
|
150
|
+
* @param spec - Multisig simulation spec.
|
|
151
|
+
* @returns The JSON-RPC multisig simulation spec with encoded configurations.
|
|
152
|
+
*/
|
|
153
|
+
export function toRpc(spec: Spec): Rpc {
|
|
154
|
+
const { account, approvals, config } = spec
|
|
155
|
+
assertApprovalCount(approvals)
|
|
156
|
+
return {
|
|
157
|
+
account,
|
|
158
|
+
approvals: approvals.map((approval) => {
|
|
159
|
+
if (approval.type === 'primitive')
|
|
160
|
+
return {
|
|
161
|
+
...approval,
|
|
162
|
+
...(typeof approval.keyData !== 'undefined'
|
|
163
|
+
? { keyData: shimKeyData(approval.keyData) }
|
|
164
|
+
: {}),
|
|
165
|
+
}
|
|
166
|
+
assertApprovalCount(approval.spec.approvals)
|
|
167
|
+
return {
|
|
168
|
+
spec: {
|
|
169
|
+
account: approval.spec.account,
|
|
170
|
+
approvals: approval.spec.approvals.map((approval) => ({
|
|
171
|
+
...approval,
|
|
172
|
+
...(typeof approval.keyData !== 'undefined'
|
|
173
|
+
? { keyData: shimKeyData(approval.keyData) }
|
|
174
|
+
: {}),
|
|
175
|
+
})),
|
|
176
|
+
config: serializeConfig(approval.spec.config),
|
|
177
|
+
},
|
|
178
|
+
type: 'multisig',
|
|
179
|
+
}
|
|
180
|
+
}),
|
|
181
|
+
config: serializeConfig(config),
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export declare namespace toRpc {
|
|
186
|
+
/** Error type for `toRpc`. */
|
|
187
|
+
export type ErrorType =
|
|
188
|
+
| Hex.fromNumber.ErrorType
|
|
189
|
+
| InvalidSimulationError
|
|
190
|
+
| MultisigConfig.assert.ErrorType
|
|
191
|
+
| Rlp.fromHex.ErrorType
|
|
192
|
+
| Errors.GlobalErrorType
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** @internal */
|
|
196
|
+
function assertApprovalCount(approvals: readonly unknown[]) {
|
|
197
|
+
if (approvals.length > MultisigConfig.maxSignatures)
|
|
198
|
+
throw new InvalidSimulationError({
|
|
199
|
+
reason: `approval count exceeds ${MultisigConfig.maxSignatures}`,
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** @internal */
|
|
204
|
+
function deserializeConfig(config: Hex.Hex): MultisigConfig.Config {
|
|
205
|
+
const tuple = Rlp.toHex(config)
|
|
206
|
+
if (!Array.isArray(tuple) || tuple.length !== 4)
|
|
207
|
+
throw new InvalidSimulationError({ reason: 'invalid config encoding' })
|
|
208
|
+
const [salt, version, threshold, owners] = tuple
|
|
209
|
+
if (
|
|
210
|
+
Array.isArray(salt) ||
|
|
211
|
+
Hex.size(salt) !== 32 ||
|
|
212
|
+
Array.isArray(version) ||
|
|
213
|
+
Hex.size(version) > 8 ||
|
|
214
|
+
(version !== '0x' && Hex.slice(version, 0, 1) === '0x00') ||
|
|
215
|
+
Array.isArray(threshold) ||
|
|
216
|
+
Hex.size(threshold) > 1 ||
|
|
217
|
+
!Array.isArray(owners) ||
|
|
218
|
+
owners.some(
|
|
219
|
+
(owner) =>
|
|
220
|
+
!Array.isArray(owner) ||
|
|
221
|
+
owner.length !== 2 ||
|
|
222
|
+
owner.some(Array.isArray) ||
|
|
223
|
+
!Address.validate(owner[0]) ||
|
|
224
|
+
Hex.size(owner[1] as Hex.Hex) > 1,
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
throw new InvalidSimulationError({ reason: 'invalid config encoding' })
|
|
228
|
+
const value = MultisigConfig.fromTuple(
|
|
229
|
+
tuple as unknown as MultisigConfig.Tuple,
|
|
230
|
+
)
|
|
231
|
+
MultisigConfig.assert(value)
|
|
232
|
+
if (!Hex.isEqual(config, serializeConfig(value)))
|
|
233
|
+
throw new InvalidSimulationError({ reason: 'noncanonical config encoding' })
|
|
234
|
+
return value
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** @internal */
|
|
238
|
+
function serializeConfig(config: MultisigConfig.Input): Hex.Hex {
|
|
239
|
+
return Rlp.fromHex(MultisigConfig.toTuple(config))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Shims long key data into the length hint accepted by Tempo's gas estimator.
|
|
244
|
+
* @internal
|
|
245
|
+
*/
|
|
246
|
+
function shimKeyData(data: Hex.Hex): Hex.Hex {
|
|
247
|
+
const size = Hex.size(data)
|
|
248
|
+
if (size <= 4) return data
|
|
249
|
+
return Hex.fromNumber(size, { size: 2 })
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Thrown when a native multisig simulation spec is invalid. */
|
|
253
|
+
export class InvalidSimulationError extends Errors.BaseError {
|
|
254
|
+
override readonly name = 'MultisigSimulation.InvalidSimulationError'
|
|
255
|
+
constructor(options: InvalidSimulationError.Options) {
|
|
256
|
+
super(`Invalid multisig simulation: ${options.reason}.`)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export declare namespace InvalidSimulationError {
|
|
261
|
+
/** Error construction options. */
|
|
262
|
+
export type Options = {
|
|
263
|
+
/** Validation failure. */
|
|
264
|
+
reason: string
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import type { Provider } from 'ox'
|
|
2
2
|
import type {
|
|
3
3
|
KeyAuthorization,
|
|
4
|
+
MultisigConfig,
|
|
4
5
|
MultisigOperation,
|
|
5
|
-
|
|
6
|
+
MultisigSimulation,
|
|
6
7
|
RpcSchemaTempo,
|
|
7
8
|
} from 'ox/tempo'
|
|
8
9
|
import { expectTypeOf, test } from 'vitest'
|
|
10
|
+
import type * as Address from '../core/Address.js'
|
|
9
11
|
import type * as Hex from '../core/Hex.js'
|
|
10
12
|
|
|
11
13
|
declare const provider: Provider.Provider<{ schema: RpcSchemaTempo.Multisig }>
|
|
12
14
|
declare const tempoProvider: Provider.Provider<{
|
|
13
15
|
schema: RpcSchemaTempo.Tempo
|
|
14
16
|
}>
|
|
17
|
+
declare const address: Address.Address
|
|
15
18
|
declare const hash: Hex.Hex
|
|
16
19
|
declare const keyAuthorization: KeyAuthorization.Rpc
|
|
17
|
-
declare const
|
|
20
|
+
declare const multisigSimulation: MultisigSimulation.Rpc
|
|
18
21
|
declare const serializedTransaction: Hex.Hex
|
|
19
22
|
declare const signature: Hex.Hex
|
|
20
23
|
|
|
@@ -52,6 +55,13 @@ test('multisig provider methods', () => {
|
|
|
52
55
|
}),
|
|
53
56
|
).resolves.toEqualTypeOf<MultisigOperation.KeyAuthorizationRpc>()
|
|
54
57
|
|
|
58
|
+
expectTypeOf(
|
|
59
|
+
provider.request({
|
|
60
|
+
method: 'multisig_getConfig',
|
|
61
|
+
params: [{ address }],
|
|
62
|
+
}),
|
|
63
|
+
).resolves.toEqualTypeOf<MultisigConfig.Rpc | null>()
|
|
64
|
+
|
|
55
65
|
expectTypeOf(
|
|
56
66
|
provider.request({
|
|
57
67
|
method: 'multisig_getOperation',
|
|
@@ -60,7 +70,7 @@ test('multisig provider methods', () => {
|
|
|
60
70
|
).resolves.toEqualTypeOf<MultisigOperation.Rpc | null>()
|
|
61
71
|
})
|
|
62
72
|
|
|
63
|
-
test('tempo simulation accepts multisig
|
|
73
|
+
test('tempo simulation accepts multisig specs', () => {
|
|
64
74
|
tempoProvider.request({
|
|
65
75
|
method: 'tempo_simulateV1',
|
|
66
76
|
params: [
|
|
@@ -69,7 +79,7 @@ test('tempo simulation accepts multisig witnesses', () => {
|
|
|
69
79
|
{
|
|
70
80
|
calls: [
|
|
71
81
|
{
|
|
72
|
-
|
|
82
|
+
multisigSimulation,
|
|
73
83
|
},
|
|
74
84
|
],
|
|
75
85
|
},
|
package/tempo/RpcSchemaTempo.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type * as Address from '../core/Address.js'
|
|
1
2
|
import type * as Block from '../core/Block.js'
|
|
2
3
|
import type * as BlockOverrides from '../core/BlockOverrides.js'
|
|
3
4
|
import type * as Hex from '../core/Hex.js'
|
|
@@ -5,6 +6,7 @@ import type * as Log from '../core/Log.js'
|
|
|
5
6
|
import type * as RpcSchema from '../core/RpcSchema.js'
|
|
6
7
|
import type * as StateOverrides from '../core/StateOverrides.js'
|
|
7
8
|
import type * as KeyAuthorization from './KeyAuthorization.js'
|
|
9
|
+
import type * as MultisigConfig from './MultisigConfig.js'
|
|
8
10
|
import type * as MultisigOperation from './MultisigOperation.js'
|
|
9
11
|
import type * as TransactionRequest from './TransactionRequest.js'
|
|
10
12
|
|
|
@@ -82,6 +84,13 @@ export type Multisig = RpcSchema.From<
|
|
|
82
84
|
}
|
|
83
85
|
ReturnType: MultisigOperation.KeyAuthorizationRpc
|
|
84
86
|
}
|
|
87
|
+
| {
|
|
88
|
+
Request: {
|
|
89
|
+
method: 'multisig_getConfig'
|
|
90
|
+
params: [{ address: Address.Address }]
|
|
91
|
+
}
|
|
92
|
+
ReturnType: MultisigConfig.Rpc | null
|
|
93
|
+
}
|
|
85
94
|
| {
|
|
86
95
|
Request: {
|
|
87
96
|
method: 'multisig_getOperation'
|
|
@@ -34,20 +34,10 @@ test('toRpc preserves the signature type', () => {
|
|
|
34
34
|
).toEqualTypeOf<SignatureEnvelope.Secp256k1Rpc>()
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
test('MultisigRpc
|
|
38
|
-
const rpc =
|
|
39
|
-
account: '0x1111111111111111111111111111111111111111',
|
|
40
|
-
config: { ...config, version: 0 },
|
|
41
|
-
signatures: [signatureRpc],
|
|
42
|
-
} as const satisfies SignatureEnvelope.MultisigRpc
|
|
37
|
+
test('MultisigRpc contains an untagged serialized multisig signature', () => {
|
|
38
|
+
const rpc = '0xf8' as const satisfies SignatureEnvelope.MultisigRpc
|
|
43
39
|
|
|
44
|
-
expectTypeOf(rpc
|
|
45
|
-
expectTypeOf(rpc.signatures).toMatchTypeOf<
|
|
46
|
-
readonly SignatureEnvelope.SignatureEnvelopeRpc[]
|
|
47
|
-
>()
|
|
48
|
-
expectTypeOf<
|
|
49
|
-
SignatureEnvelope.GetType<typeof rpc>
|
|
50
|
-
>().toEqualTypeOf<'multisig'>()
|
|
40
|
+
expectTypeOf(rpc).toMatchTypeOf<`0x${string}`>()
|
|
51
41
|
})
|
|
52
42
|
|
|
53
43
|
test('from derives an initial account', () => {
|
|
@@ -75,40 +65,14 @@ test('from requires an account for a current config', () => {
|
|
|
75
65
|
expectTypeOf(multisig).toMatchTypeOf<SignatureEnvelope.Multisig>()
|
|
76
66
|
})
|
|
77
67
|
|
|
78
|
-
test('MultisigRpc rejects
|
|
79
|
-
const
|
|
80
|
-
account: '0x1111111111111111111111111111111111111111',
|
|
81
|
-
signatures: [signatureRpc],
|
|
82
|
-
} as const
|
|
83
|
-
// @ts-expect-error Multisig RPC signatures require config.
|
|
84
|
-
const accountOnlyRpc: SignatureEnvelope.MultisigRpc = accountOnly
|
|
85
|
-
|
|
86
|
-
const init = {
|
|
87
|
-
init: config,
|
|
88
|
-
signatures: [signatureRpc],
|
|
89
|
-
} as const
|
|
90
|
-
// @ts-expect-error Multisig RPC signatures no longer accept init.
|
|
91
|
-
const initRpc: SignatureEnvelope.MultisigRpc = init
|
|
92
|
-
|
|
93
|
-
const serialized = {
|
|
94
|
-
account: '0x1111111111111111111111111111111111111111',
|
|
95
|
-
config: { ...config, version: 0 },
|
|
96
|
-
signatures: ['0x1234'],
|
|
97
|
-
} as const
|
|
98
|
-
// @ts-expect-error Owner approvals use structured RPC envelopes.
|
|
99
|
-
const serializedRpc: SignatureEnvelope.MultisigRpc = serialized
|
|
100
|
-
|
|
101
|
-
const tagged = {
|
|
68
|
+
test('MultisigRpc rejects structured signatures', () => {
|
|
69
|
+
const structured = {
|
|
102
70
|
account: '0x1111111111111111111111111111111111111111',
|
|
103
71
|
config: { ...config, version: 0 },
|
|
104
72
|
signatures: [signatureRpc],
|
|
105
|
-
type: 'multisig',
|
|
106
73
|
} as const
|
|
107
|
-
// @ts-expect-error Multisig RPC signatures are
|
|
108
|
-
const
|
|
74
|
+
// @ts-expect-error Multisig RPC signatures are serialized hex.
|
|
75
|
+
const rpc: SignatureEnvelope.MultisigRpc = structured
|
|
109
76
|
|
|
110
|
-
expectTypeOf(
|
|
111
|
-
expectTypeOf(initRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
112
|
-
expectTypeOf(serializedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
113
|
-
expectTypeOf(taggedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
77
|
+
expectTypeOf(rpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
114
78
|
})
|
|
@@ -68,6 +68,9 @@ const signature_keychain_webauthn = SignatureEnvelope.from({
|
|
|
68
68
|
version: 'v2',
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
const signature_multisig_rpc =
|
|
72
|
+
'0xf897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b' as const
|
|
73
|
+
|
|
71
74
|
describe('assert', () => {
|
|
72
75
|
describe('secp256k1', () => {
|
|
73
76
|
test('behavior: validates valid signature', () => {
|
|
@@ -2468,6 +2471,72 @@ describe('fromRpc', () => {
|
|
|
2468
2471
|
expect(envelope.keyId).toBe('0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c')
|
|
2469
2472
|
})
|
|
2470
2473
|
})
|
|
2474
|
+
|
|
2475
|
+
describe('multisig', () => {
|
|
2476
|
+
test('behavior: converts an untagged serialized multisig signature', () => {
|
|
2477
|
+
expect(
|
|
2478
|
+
SignatureEnvelope.fromRpc(signature_multisig_rpc),
|
|
2479
|
+
).toMatchInlineSnapshot(`
|
|
2480
|
+
{
|
|
2481
|
+
"account": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
|
|
2482
|
+
"config": {
|
|
2483
|
+
"owners": [
|
|
2484
|
+
{
|
|
2485
|
+
"owner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
2486
|
+
"weight": 1,
|
|
2487
|
+
},
|
|
2488
|
+
],
|
|
2489
|
+
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
2490
|
+
"threshold": 1,
|
|
2491
|
+
"version": 0n,
|
|
2492
|
+
},
|
|
2493
|
+
"signatures": [
|
|
2494
|
+
{
|
|
2495
|
+
"signature": {
|
|
2496
|
+
"r": 60871800714128149016591846789173983752390955456021923556141688418770269698437n,
|
|
2497
|
+
"s": 24367763726302312398084372528434045669788111037623467391175388828437596076276n,
|
|
2498
|
+
"yParity": 0,
|
|
2499
|
+
},
|
|
2500
|
+
"type": "secp256k1",
|
|
2501
|
+
},
|
|
2502
|
+
],
|
|
2503
|
+
"type": "multisig",
|
|
2504
|
+
}
|
|
2505
|
+
`)
|
|
2506
|
+
})
|
|
2507
|
+
|
|
2508
|
+
test('error: rejects a payload with the transaction type byte', () => {
|
|
2509
|
+
expect(() =>
|
|
2510
|
+
SignatureEnvelope.fromRpc(`0x05${signature_multisig_rpc.slice(2)}`),
|
|
2511
|
+
).toThrowErrorMatchingInlineSnapshot(`
|
|
2512
|
+
[SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: invalid multisig wire shape: expected exactly three fields
|
|
2513
|
+
|
|
2514
|
+
Serialized: 0x0505f897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b]
|
|
2515
|
+
`)
|
|
2516
|
+
})
|
|
2517
|
+
|
|
2518
|
+
test('error: rejects trailing bytes', () => {
|
|
2519
|
+
expect(() =>
|
|
2520
|
+
SignatureEnvelope.fromRpc(`${signature_multisig_rpc}00`),
|
|
2521
|
+
).toThrowErrorMatchingInlineSnapshot(`
|
|
2522
|
+
[SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: invalid multisig RPC signature
|
|
2523
|
+
|
|
2524
|
+
Serialized: 0xf897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b00]
|
|
2525
|
+
`)
|
|
2526
|
+
})
|
|
2527
|
+
|
|
2528
|
+
test('error: rejects a structured multisig signature', () => {
|
|
2529
|
+
expect(() =>
|
|
2530
|
+
SignatureEnvelope.fromRpc({
|
|
2531
|
+
account: '0x9dba7f426b711d4893c11611eacf7cc334e7146b',
|
|
2532
|
+
config: {},
|
|
2533
|
+
signatures: [],
|
|
2534
|
+
} as never),
|
|
2535
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
2536
|
+
`[SignatureEnvelope.CoercionError: Unable to coerce value (\`{"account":"0x9dba7f426b711d4893c11611eacf7cc334e7146b","config":{},"signatures":[]}\`) to a valid signature envelope.]`,
|
|
2537
|
+
)
|
|
2538
|
+
})
|
|
2539
|
+
})
|
|
2471
2540
|
})
|
|
2472
2541
|
|
|
2473
2542
|
describe('toRpc', () => {
|
|
@@ -2565,6 +2634,7 @@ describe('toRpc', () => {
|
|
|
2565
2634
|
const rpc = SignatureEnvelope.toRpc(
|
|
2566
2635
|
signature_keychain_p256,
|
|
2567
2636
|
) as SignatureEnvelope.KeychainRpc
|
|
2637
|
+
if (typeof rpc.signature === 'string') throw new Error('unreachable')
|
|
2568
2638
|
|
|
2569
2639
|
expect(rpc.type).toBe('keychain')
|
|
2570
2640
|
expect(rpc.userAddress).toBe(signature_keychain_p256.userAddress)
|
|
@@ -2577,6 +2647,7 @@ describe('toRpc', () => {
|
|
|
2577
2647
|
const rpc = SignatureEnvelope.toRpc(
|
|
2578
2648
|
signature_keychain_webauthn,
|
|
2579
2649
|
) as SignatureEnvelope.KeychainRpc
|
|
2650
|
+
if (typeof rpc.signature === 'string') throw new Error('unreachable')
|
|
2580
2651
|
|
|
2581
2652
|
expect(rpc.type).toBe('keychain')
|
|
2582
2653
|
expect(rpc.userAddress).toBe(signature_keychain_webauthn.userAddress)
|
|
@@ -2620,6 +2691,18 @@ describe('toRpc', () => {
|
|
|
2620
2691
|
expect(rpc.keyId).toBeUndefined()
|
|
2621
2692
|
})
|
|
2622
2693
|
})
|
|
2694
|
+
|
|
2695
|
+
describe('multisig', () => {
|
|
2696
|
+
test('behavior: removes the transaction type byte', () => {
|
|
2697
|
+
const envelope = SignatureEnvelope.deserialize(
|
|
2698
|
+
`0x05${signature_multisig_rpc.slice(2)}`,
|
|
2699
|
+
)
|
|
2700
|
+
|
|
2701
|
+
expect(SignatureEnvelope.toRpc(envelope)).toMatchInlineSnapshot(
|
|
2702
|
+
`"${signature_multisig_rpc}"`,
|
|
2703
|
+
)
|
|
2704
|
+
})
|
|
2705
|
+
})
|
|
2623
2706
|
})
|
|
2624
2707
|
|
|
2625
2708
|
describe('roundtrip: toRpc <-> fromRpc', () => {
|
|
@@ -2860,7 +2943,7 @@ describe('multisig', () => {
|
|
|
2860
2943
|
const nested =
|
|
2861
2944
|
'0x05f8f0949969e2243075b27a8eab61009c59b77ab13b83f6f83ba033333333333333333333333333333333333333333333333333333333333333338001d7d6944f9f5b162a7464bcc260ce44d7ae0d935f9c583701f89cb89a05f897944f9f5b162a7464bcc260ce44d7ae0d935f9c5837f83ba022222222222222222222222222222222222222222222222222222222222222228001d7d6946813eb9362372eef6200f3b1dbc3f819671cba6901f843b841032aa6f3ea7b0b7069720d0f3891983c493d149326c5c957d864ed7371b8475e3e95325079b24491f4b6d68920e4b3bacd7c6094df6ed88b8674864ab559c8fb1b' as const
|
|
2862
2945
|
|
|
2863
|
-
test('example: decodes and re-encodes the frozen initial
|
|
2946
|
+
test('example: decodes and re-encodes the frozen initial signature', () => {
|
|
2864
2947
|
const envelope = SignatureEnvelope.deserialize(initial)
|
|
2865
2948
|
|
|
2866
2949
|
expect(envelope).toMatchInlineSnapshot(`
|
|
@@ -2893,7 +2976,7 @@ describe('multisig', () => {
|
|
|
2893
2976
|
expect(SignatureEnvelope.serialize(envelope)).toBe(initial)
|
|
2894
2977
|
})
|
|
2895
2978
|
|
|
2896
|
-
test('example: decodes and re-encodes the frozen current
|
|
2979
|
+
test('example: decodes and re-encodes the frozen current signature', () => {
|
|
2897
2980
|
const envelope = SignatureEnvelope.deserialize(current)
|
|
2898
2981
|
|
|
2899
2982
|
expect(envelope).toMatchInlineSnapshot(`
|
|
@@ -2926,7 +3009,7 @@ describe('multisig', () => {
|
|
|
2926
3009
|
expect(SignatureEnvelope.serialize(envelope)).toBe(current)
|
|
2927
3010
|
})
|
|
2928
3011
|
|
|
2929
|
-
test('example: decodes and re-encodes the frozen nested
|
|
3012
|
+
test('example: decodes and re-encodes the frozen nested signature', () => {
|
|
2930
3013
|
const envelope = SignatureEnvelope.deserialize(nested)
|
|
2931
3014
|
|
|
2932
3015
|
expect(envelope).toMatchInlineSnapshot(`
|
|
@@ -3014,7 +3097,7 @@ describe('multisig', () => {
|
|
|
3014
3097
|
`)
|
|
3015
3098
|
})
|
|
3016
3099
|
|
|
3017
|
-
test('behavior: round trips initial and current RPC
|
|
3100
|
+
test('behavior: round trips initial and current RPC signatures', () => {
|
|
3018
3101
|
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3019
3102
|
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3020
3103
|
const rpc = {
|
|
@@ -3024,50 +3107,8 @@ describe('multisig', () => {
|
|
|
3024
3107
|
|
|
3025
3108
|
expect(rpc).toMatchInlineSnapshot(`
|
|
3026
3109
|
{
|
|
3027
|
-
"current":
|
|
3028
|
-
|
|
3029
|
-
"config": {
|
|
3030
|
-
"owners": [
|
|
3031
|
-
{
|
|
3032
|
-
"owner": "0x2b5ad5c4795c026514f8317c7a215e218dccd6cf",
|
|
3033
|
-
"weight": 1,
|
|
3034
|
-
},
|
|
3035
|
-
],
|
|
3036
|
-
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3037
|
-
"threshold": 1,
|
|
3038
|
-
"version": 1,
|
|
3039
|
-
},
|
|
3040
|
-
"signatures": [
|
|
3041
|
-
{
|
|
3042
|
-
"r": "0x4a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e050",
|
|
3043
|
-
"s": "0x4a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf5",
|
|
3044
|
-
"type": "secp256k1",
|
|
3045
|
-
"yParity": "0x0",
|
|
3046
|
-
},
|
|
3047
|
-
],
|
|
3048
|
-
},
|
|
3049
|
-
"initial": {
|
|
3050
|
-
"account": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
|
|
3051
|
-
"config": {
|
|
3052
|
-
"owners": [
|
|
3053
|
-
{
|
|
3054
|
-
"owner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
3055
|
-
"weight": 1,
|
|
3056
|
-
},
|
|
3057
|
-
],
|
|
3058
|
-
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3059
|
-
"threshold": 1,
|
|
3060
|
-
"version": 0,
|
|
3061
|
-
},
|
|
3062
|
-
"signatures": [
|
|
3063
|
-
{
|
|
3064
|
-
"r": "0x869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f985",
|
|
3065
|
-
"s": "0x35dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f4",
|
|
3066
|
-
"type": "secp256k1",
|
|
3067
|
-
"yParity": "0x0",
|
|
3068
|
-
},
|
|
3069
|
-
],
|
|
3070
|
-
},
|
|
3110
|
+
"current": "0xf897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d6942b5ad5c4795c026514f8317c7a215e218dccd6cf01f843b8414a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e0504a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf51b",
|
|
3111
|
+
"initial": "0xf897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b",
|
|
3071
3112
|
}
|
|
3072
3113
|
`)
|
|
3073
3114
|
expect(SignatureEnvelope.fromRpc(rpc.initial)).toStrictEqual(
|
|
@@ -3078,7 +3119,7 @@ describe('multisig', () => {
|
|
|
3078
3119
|
)
|
|
3079
3120
|
})
|
|
3080
3121
|
|
|
3081
|
-
test('behavior: derives the account only for an initial
|
|
3122
|
+
test('behavior: derives the account only for an initial config', () => {
|
|
3082
3123
|
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3083
3124
|
if (initialEnvelope.type !== 'multisig') throw new Error('unreachable')
|
|
3084
3125
|
if (initialEnvelope.config.version !== 0n) throw new Error('unreachable')
|
|
@@ -3149,7 +3190,7 @@ describe('multisig', () => {
|
|
|
3149
3190
|
`)
|
|
3150
3191
|
})
|
|
3151
3192
|
|
|
3152
|
-
test('behavior: accepts initial and current nested
|
|
3193
|
+
test('behavior: accepts initial and current nested signatures', () => {
|
|
3153
3194
|
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3154
3195
|
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3155
3196
|
if (
|
|
@@ -3191,7 +3232,7 @@ describe('multisig', () => {
|
|
|
3191
3232
|
)
|
|
3192
3233
|
})
|
|
3193
3234
|
|
|
3194
|
-
test('error: rejects a self-owned current
|
|
3235
|
+
test('error: rejects a self-owned current signature', () => {
|
|
3195
3236
|
const envelope = SignatureEnvelope.deserialize(current)
|
|
3196
3237
|
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3197
3238
|
const account = envelope.config.owners[0]!.owner
|