ox 1.7.4 → 1.8.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.
Files changed (237) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/core/AbiEvent.d.ts.map +1 -1
  3. package/dist/core/AbiEvent.js +10 -9
  4. package/dist/core/AbiEvent.js.map +1 -1
  5. package/dist/core/Blobs.d.ts.map +1 -1
  6. package/dist/core/Blobs.js +6 -2
  7. package/dist/core/Blobs.js.map +1 -1
  8. package/dist/core/Frame.d.ts +290 -0
  9. package/dist/core/Frame.d.ts.map +1 -0
  10. package/dist/core/Frame.js +306 -0
  11. package/dist/core/Frame.js.map +1 -0
  12. package/dist/core/FrameReceipt.d.ts +92 -0
  13. package/dist/core/FrameReceipt.d.ts.map +1 -0
  14. package/dist/core/FrameReceipt.js +72 -0
  15. package/dist/core/FrameReceipt.js.map +1 -0
  16. package/dist/core/FrameSignature.d.ts +357 -0
  17. package/dist/core/FrameSignature.d.ts.map +1 -0
  18. package/dist/core/FrameSignature.js +411 -0
  19. package/dist/core/FrameSignature.js.map +1 -0
  20. package/dist/core/Signature.d.ts +6 -5
  21. package/dist/core/Signature.d.ts.map +1 -1
  22. package/dist/core/Signature.js +17 -7
  23. package/dist/core/Signature.js.map +1 -1
  24. package/dist/core/Siwe.d.ts.map +1 -1
  25. package/dist/core/Siwe.js +21 -1
  26. package/dist/core/Siwe.js.map +1 -1
  27. package/dist/core/Transaction.d.ts +29 -4
  28. package/dist/core/Transaction.d.ts.map +1 -1
  29. package/dist/core/Transaction.js +25 -4
  30. package/dist/core/Transaction.js.map +1 -1
  31. package/dist/core/TransactionReceipt.d.ts +13 -4
  32. package/dist/core/TransactionReceipt.d.ts.map +1 -1
  33. package/dist/core/TransactionReceipt.js +14 -0
  34. package/dist/core/TransactionReceipt.js.map +1 -1
  35. package/dist/core/TransactionRequest.d.ts +15 -7
  36. package/dist/core/TransactionRequest.d.ts.map +1 -1
  37. package/dist/core/TransactionRequest.js +47 -1
  38. package/dist/core/TransactionRequest.js.map +1 -1
  39. package/dist/core/TxEnvelope.d.ts +35 -21
  40. package/dist/core/TxEnvelope.d.ts.map +1 -1
  41. package/dist/core/TxEnvelope.js +46 -6
  42. package/dist/core/TxEnvelope.js.map +1 -1
  43. package/dist/core/TxEnvelopeEip8141.d.ts +341 -0
  44. package/dist/core/TxEnvelopeEip8141.d.ts.map +1 -0
  45. package/dist/core/TxEnvelopeEip8141.js +485 -0
  46. package/dist/core/TxEnvelopeEip8141.js.map +1 -0
  47. package/dist/core/internal/curves.d.ts +2 -0
  48. package/dist/core/internal/curves.d.ts.map +1 -1
  49. package/dist/core/internal/curves.js +2 -0
  50. package/dist/core/internal/curves.js.map +1 -1
  51. package/dist/index.d.ts +73 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +73 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/tempo/KeyAuthorization.d.ts +42 -38
  56. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  57. package/dist/tempo/KeyAuthorization.js +24 -4
  58. package/dist/tempo/KeyAuthorization.js.map +1 -1
  59. package/dist/tempo/MultisigConfig.d.ts +18 -15
  60. package/dist/tempo/MultisigConfig.d.ts.map +1 -1
  61. package/dist/tempo/MultisigConfig.js +49 -20
  62. package/dist/tempo/MultisigConfig.js.map +1 -1
  63. package/dist/tempo/MultisigOperation.d.ts +3 -2
  64. package/dist/tempo/MultisigOperation.d.ts.map +1 -1
  65. package/dist/tempo/MultisigOperation.js +20 -100
  66. package/dist/tempo/MultisigOperation.js.map +1 -1
  67. package/dist/tempo/MultisigSimulation.d.ts +2 -49
  68. package/dist/tempo/MultisigSimulation.d.ts.map +1 -1
  69. package/dist/tempo/MultisigSimulation.js +18 -46
  70. package/dist/tempo/MultisigSimulation.js.map +1 -1
  71. package/dist/tempo/SignatureEnvelope.d.ts +72 -140
  72. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  73. package/dist/tempo/SignatureEnvelope.js +130 -181
  74. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  75. package/dist/tempo/Transaction.d.ts +2 -0
  76. package/dist/tempo/Transaction.d.ts.map +1 -1
  77. package/dist/tempo/TransactionReceipt.d.ts +2 -0
  78. package/dist/tempo/TransactionReceipt.d.ts.map +1 -1
  79. package/dist/tempo/TransactionRequest.d.ts +4 -2
  80. package/dist/tempo/TransactionRequest.d.ts.map +1 -1
  81. package/dist/tempo/TransactionRequest.js +6 -1
  82. package/dist/tempo/TransactionRequest.js.map +1 -1
  83. package/dist/tempo/index.d.ts +3 -1
  84. package/dist/tempo/index.d.ts.map +1 -1
  85. package/dist/tempo/index.js +3 -1
  86. package/dist/tempo/index.js.map +1 -1
  87. package/dist/zod/Block.d.ts +123 -0
  88. package/dist/zod/Block.d.ts.map +1 -1
  89. package/dist/zod/Frame.d.ts +73 -0
  90. package/dist/zod/Frame.d.ts.map +1 -0
  91. package/dist/zod/Frame.js +74 -0
  92. package/dist/zod/Frame.js.map +1 -0
  93. package/dist/zod/FrameReceipt.d.ts +50 -0
  94. package/dist/zod/FrameReceipt.d.ts.map +1 -0
  95. package/dist/zod/FrameReceipt.js +37 -0
  96. package/dist/zod/FrameReceipt.js.map +1 -0
  97. package/dist/zod/FrameSignature.d.ts +63 -0
  98. package/dist/zod/FrameSignature.d.ts.map +1 -0
  99. package/dist/zod/FrameSignature.js +66 -0
  100. package/dist/zod/FrameSignature.js.map +1 -0
  101. package/dist/zod/RpcSchema.d.ts +806 -11
  102. package/dist/zod/RpcSchema.d.ts.map +1 -1
  103. package/dist/zod/Transaction.d.ts +252 -0
  104. package/dist/zod/Transaction.d.ts.map +1 -1
  105. package/dist/zod/Transaction.js +54 -0
  106. package/dist/zod/Transaction.js.map +1 -1
  107. package/dist/zod/TransactionEnvelope.d.ts +233 -4
  108. package/dist/zod/TransactionEnvelope.d.ts.map +1 -1
  109. package/dist/zod/TransactionEnvelope.js +9 -0
  110. package/dist/zod/TransactionEnvelope.js.map +1 -1
  111. package/dist/zod/TransactionReceipt.d.ts +48 -0
  112. package/dist/zod/TransactionReceipt.d.ts.map +1 -1
  113. package/dist/zod/TransactionReceipt.js +8 -3
  114. package/dist/zod/TransactionReceipt.js.map +1 -1
  115. package/dist/zod/TransactionRequest.d.ts +50 -2
  116. package/dist/zod/TransactionRequest.d.ts.map +1 -1
  117. package/dist/zod/TransactionRequest.js +10 -4
  118. package/dist/zod/TransactionRequest.js.map +1 -1
  119. package/dist/zod/TxEnvelopeEip8141.d.ts +166 -0
  120. package/dist/zod/TxEnvelopeEip8141.d.ts.map +1 -0
  121. package/dist/zod/TxEnvelopeEip8141.js +76 -0
  122. package/dist/zod/TxEnvelopeEip8141.js.map +1 -0
  123. package/dist/zod/internal/Frame.d.ts +4 -0
  124. package/dist/zod/internal/Frame.d.ts.map +1 -0
  125. package/dist/zod/internal/Frame.js +17 -0
  126. package/dist/zod/internal/Frame.js.map +1 -0
  127. package/dist/zod/internal/rpcSchemas/Eth.d.ts +842 -11
  128. package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
  129. package/dist/zod/internal/rpcSchemas/Wallet.d.ts +56 -2
  130. package/dist/zod/internal/rpcSchemas/Wallet.d.ts.map +1 -1
  131. package/dist/zod/tempo/AuthorizationTempo.d.ts +350 -10
  132. package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
  133. package/dist/zod/tempo/KeyAuthorization.d.ts +242 -422
  134. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  135. package/dist/zod/tempo/KeyAuthorization.js +29 -31
  136. package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
  137. package/dist/zod/tempo/MultisigSimulation.d.ts +8 -76
  138. package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -1
  139. package/dist/zod/tempo/MultisigSimulation.js +3 -37
  140. package/dist/zod/tempo/MultisigSimulation.js.map +1 -1
  141. package/dist/zod/tempo/RpcSchemaTempo.d.ts +424 -228
  142. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  143. package/dist/zod/tempo/SignatureEnvelope.d.ts +105 -3
  144. package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
  145. package/dist/zod/tempo/SignatureEnvelope.js +21 -9
  146. package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
  147. package/dist/zod/tempo/Transaction.d.ts +783 -432
  148. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  149. package/dist/zod/tempo/TransactionRequest.d.ts +590 -473
  150. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  151. package/dist/zod/tempo/TransactionRequest.js +8 -0
  152. package/dist/zod/tempo/TransactionRequest.js.map +1 -1
  153. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +327 -213
  154. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  155. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +105 -3
  156. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
  157. package/dist/zod/z.d.ts +4 -0
  158. package/dist/zod/z.d.ts.map +1 -1
  159. package/dist/zod/z.js +4 -0
  160. package/dist/zod/z.js.map +1 -1
  161. package/package.json +41 -1
  162. package/src/core/AbiEvent.ts +11 -9
  163. package/src/core/Blobs.ts +6 -2
  164. package/src/core/Frame.ts +443 -0
  165. package/src/core/FrameReceipt.ts +117 -0
  166. package/src/core/FrameSignature.ts +593 -0
  167. package/src/core/Signature.ts +41 -11
  168. package/src/core/Siwe.ts +20 -1
  169. package/src/core/Transaction.ts +82 -5
  170. package/src/core/TransactionReceipt.ts +41 -3
  171. package/src/core/TransactionRequest.ts +71 -4
  172. package/src/core/TxEnvelope.ts +159 -90
  173. package/src/core/TxEnvelopeEip8141.ts +715 -0
  174. package/src/core/_test/AbiEvent.test.ts +45 -0
  175. package/src/core/_test/Blobs.test.ts +22 -0
  176. package/src/core/_test/Frame.test-d.ts +96 -0
  177. package/src/core/_test/Frame.test.ts +267 -0
  178. package/src/core/_test/FrameReceipt.test.ts +86 -0
  179. package/src/core/_test/FrameSignature.test-d.ts +93 -0
  180. package/src/core/_test/FrameSignature.test.ts +554 -0
  181. package/src/core/_test/Signature.test.ts +166 -0
  182. package/src/core/_test/Siwe.test.ts +100 -1
  183. package/src/core/_test/Transaction.test.ts +33 -0
  184. package/src/core/_test/TransactionEnvelope.test-d.ts +74 -0
  185. package/src/core/_test/TransactionEnvelope.test.ts +107 -0
  186. package/src/core/_test/TransactionReceipt.test.ts +59 -0
  187. package/src/core/_test/TransactionRequest.test-d.ts +50 -0
  188. package/src/core/_test/TransactionRequest.test.ts +88 -1
  189. package/src/core/_test/TxEnvelopeEip8141.test-d.ts +44 -0
  190. package/src/core/_test/TxEnvelopeEip8141.test.ts +1775 -0
  191. package/src/core/_test/curves.test.ts +6 -1
  192. package/src/core/_test/index.test.ts +4 -0
  193. package/src/core/internal/curves.ts +4 -0
  194. package/src/index.ts +77 -0
  195. package/src/tempo/AuthorizationTempo.test.ts +8 -4
  196. package/src/tempo/KeyAuthorization.test-d.ts +16 -63
  197. package/src/tempo/KeyAuthorization.test.ts +69 -11
  198. package/src/tempo/KeyAuthorization.ts +107 -63
  199. package/src/tempo/MultisigConfig.test.ts +44 -20
  200. package/src/tempo/MultisigConfig.ts +65 -23
  201. package/src/tempo/MultisigOperation.test.ts +79 -550
  202. package/src/tempo/MultisigOperation.ts +31 -146
  203. package/src/tempo/MultisigSimulation.test-d.ts +2 -2
  204. package/src/tempo/MultisigSimulation.test.ts +21 -110
  205. package/src/tempo/MultisigSimulation.ts +25 -99
  206. package/src/tempo/SignatureEnvelope.test-d.ts +29 -47
  207. package/src/tempo/SignatureEnvelope.test.ts +328 -541
  208. package/src/tempo/SignatureEnvelope.ts +196 -291
  209. package/src/tempo/Transaction.test.ts +4 -4
  210. package/src/tempo/TransactionRequest.test.ts +17 -80
  211. package/src/tempo/TransactionRequest.ts +19 -2
  212. package/src/tempo/index.ts +3 -1
  213. package/src/tempo/multisig.e2e.test.ts +321 -860
  214. package/src/version.ts +1 -1
  215. package/src/zod/Frame.ts +82 -0
  216. package/src/zod/FrameReceipt.ts +48 -0
  217. package/src/zod/FrameSignature.ts +78 -0
  218. package/src/zod/Transaction.ts +63 -0
  219. package/src/zod/TransactionEnvelope.ts +33 -0
  220. package/src/zod/TransactionReceipt.ts +14 -3
  221. package/src/zod/TransactionRequest.ts +18 -3
  222. package/src/zod/TxEnvelopeEip8141.ts +102 -0
  223. package/src/zod/_test/Frame.test-d.ts +41 -0
  224. package/src/zod/_test/Frame.test.ts +54 -0
  225. package/src/zod/_test/FrameReceipt.test.ts +75 -0
  226. package/src/zod/_test/FrameSignature.test.ts +94 -0
  227. package/src/zod/_test/TxEnvelopeEip8141.test.ts +116 -0
  228. package/src/zod/internal/Frame.ts +26 -0
  229. package/src/zod/tempo/KeyAuthorization.ts +50 -39
  230. package/src/zod/tempo/MultisigSimulation.ts +3 -48
  231. package/src/zod/tempo/SignatureEnvelope.ts +46 -35
  232. package/src/zod/tempo/TransactionRequest.ts +14 -0
  233. package/src/zod/tempo/_test/KeyAuthorization.test.ts +69 -20
  234. package/src/zod/tempo/_test/MultisigSimulation.test.ts +6 -31
  235. package/src/zod/tempo/_test/SignatureEnvelope.test.ts +118 -85
  236. package/src/zod/tempo/_test/TransactionRequest.test.ts +9 -19
  237. package/src/zod/z.ts +4 -0
@@ -0,0 +1,554 @@
1
+ import { p256 } from '@noble/curves/nist.js'
2
+ import { secp256k1 } from '@noble/curves/secp256k1.js'
3
+ import { FrameSignature, Hex, P256, Rlp, Secp256k1 } from 'ox'
4
+ import { describe, expect, test } from 'vite-plus/test'
5
+
6
+ const arbitrary = {
7
+ payload: '0x',
8
+ scheme: 'arbitrary',
9
+ signature: '0xaabb',
10
+ } as const
11
+ const privateKey = Hex.fromNumber(1, { size: 32 })
12
+ const payload = Hex.fromNumber(1, { size: 32 })
13
+ const publicKey = {
14
+ prefix: 4,
15
+ x: '0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296',
16
+ y: '0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5',
17
+ } as const
18
+ const secpBytes =
19
+ '0x0100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'
20
+ const p256Bytes =
21
+ '0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000026b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'
22
+
23
+ describe('from', () => {
24
+ test.each(['0x', '0xaabb'] as const)(
25
+ 'constructs an arbitrary signature from %s',
26
+ (signature) => {
27
+ expect(FrameSignature.from(signature)).toEqual({
28
+ payload: '0x',
29
+ scheme: 'arbitrary',
30
+ signature,
31
+ })
32
+ },
33
+ )
34
+
35
+ test.each(['0xa', '0xgg', 'aabb'])(
36
+ 'rejects malformed signature bytes %s',
37
+ (signature) => {
38
+ expect(() => FrameSignature.from(signature as Hex.Hex)).toThrow()
39
+ },
40
+ )
41
+
42
+ test('defaults to arbitrary and the canonical transaction payload', () => {
43
+ expect(FrameSignature.from({ signature: '0xaabb' })).toMatchInlineSnapshot(`
44
+ {
45
+ "payload": "0x",
46
+ "scheme": "arbitrary",
47
+ "signature": "0xaabb",
48
+ }
49
+ `)
50
+ expect(
51
+ FrameSignature.from({
52
+ payload: undefined,
53
+ scheme: undefined,
54
+ signature: '0xaabb',
55
+ }),
56
+ ).toEqual(arbitrary)
57
+ })
58
+
59
+ test('copies metadata without resolving the signer', () => {
60
+ expect(FrameSignature.from(arbitrary)).toEqual(arbitrary)
61
+ expect(FrameSignature.from(arbitrary)).not.toBe(arbitrary)
62
+ expect(FrameSignature.from({ scheme: 1 })).toEqual({
63
+ payload: '0x',
64
+ scheme: 1,
65
+ })
66
+ })
67
+
68
+ test('preserves structured signatures and public keys', () => {
69
+ const signature = P256.sign({ extraEntropy: false, payload, privateKey })
70
+ const entry = FrameSignature.from({
71
+ payload,
72
+ publicKey,
73
+ scheme: 'p256',
74
+ signature,
75
+ })
76
+ expect(entry).toEqual({ payload, publicKey, scheme: 'p256', signature })
77
+ })
78
+
79
+ describe('p256', () => {
80
+ test('constructs a structured entry with default payload', () => {
81
+ const signature = { r: '0x01', s: '0x02' } as const
82
+ const entry = FrameSignature.from({
83
+ publicKey,
84
+ scheme: 'p256',
85
+ signature,
86
+ })
87
+ expect(entry).toEqual({
88
+ payload: '0x',
89
+ publicKey,
90
+ scheme: 'p256',
91
+ signature,
92
+ })
93
+ expect(FrameSignature.toTuple(entry)[3]).toBe(p256Bytes)
94
+ })
95
+
96
+ test('preserves metadata and high-s until encoding', () => {
97
+ const signature = Object.freeze({
98
+ r: '0x01',
99
+ s: Hex.fromNumber(p256.Point.Fn.ORDER - 2n),
100
+ })
101
+ const signer = '0x0000000000000000000000000000000000000000'
102
+ const entry = FrameSignature.from({
103
+ payload,
104
+ publicKey,
105
+ scheme: 'p256',
106
+ signature,
107
+ signer,
108
+ })
109
+ expect(entry).toEqual({
110
+ payload,
111
+ publicKey,
112
+ scheme: 'p256',
113
+ signature,
114
+ signer,
115
+ })
116
+ expect(FrameSignature.toTuple(entry)[3]).toBe(p256Bytes)
117
+ expect(entry.signature.s).toBe(signature.s)
118
+ })
119
+
120
+ test('rejects a missing public key', () => {
121
+ expect(() =>
122
+ FrameSignature.from({
123
+ scheme: 'p256',
124
+ signature: { r: '0x01', s: '0x02' },
125
+ } as never),
126
+ ).toThrow()
127
+ })
128
+ })
129
+
130
+ describe('secp256k1', () => {
131
+ test('constructs a structured entry with default payload', () => {
132
+ const signature = { r: '0x01', s: '0x02', yParity: 1 } as const
133
+ const entry = FrameSignature.from({ scheme: 'secp256k1', signature })
134
+ expect(entry).toEqual({ payload: '0x', scheme: 'secp256k1', signature })
135
+ expect(FrameSignature.toTuple(entry)[3]).toBe(secpBytes)
136
+ })
137
+
138
+ test('preserves explicit payload and signer', () => {
139
+ const signature = Secp256k1.sign({ payload, privateKey })
140
+ const signer = '0x0000000000000000000000000000000000000000'
141
+ expect(
142
+ FrameSignature.from({
143
+ payload,
144
+ scheme: 'secp256k1',
145
+ signature,
146
+ signer,
147
+ }),
148
+ ).toEqual({ payload, scheme: 'secp256k1', signature, signer })
149
+ })
150
+
151
+ test('rejects invalid parity', () => {
152
+ expect(() =>
153
+ FrameSignature.from({
154
+ scheme: 'secp256k1',
155
+ signature: { r: '0x01', s: '0x02', yParity: 27 },
156
+ }),
157
+ ).toThrow()
158
+ })
159
+ })
160
+ })
161
+
162
+ describe('toTuple', () => {
163
+ test('omitted payload selects the canonical signing hash', () => {
164
+ expect(
165
+ FrameSignature.toTuple({ scheme: 'arbitrary', signature: '0xaabb' }),
166
+ ).toEqual(['0x', '0x', '0x', '0xaabb'])
167
+ expect(FrameSignature.toTuple({ scheme: 'secp256k1' })).toEqual([
168
+ '0x01',
169
+ '0x',
170
+ '0x',
171
+ '0x',
172
+ ])
173
+ expect(
174
+ FrameSignature.toTuple({ payload: undefined, scheme: 'p256' }),
175
+ ).toEqual(['0x02', '0x', '0x', '0x'])
176
+ })
177
+
178
+ test('encodes arbitrary witness bytes in specification order', () => {
179
+ expect(FrameSignature.toTuple(arbitrary)).toEqual([
180
+ '0x',
181
+ '0x',
182
+ '0x',
183
+ '0xaabb',
184
+ ])
185
+ expect(
186
+ Rlp.fromHex(FrameSignature.toTuple(arbitrary)),
187
+ ).toMatchInlineSnapshot('"0xc680808082aabb"')
188
+ })
189
+
190
+ test.each([0, 'arbitrary'] as const)(
191
+ 'encodes arbitrary scheme %s',
192
+ (scheme) => {
193
+ expect(FrameSignature.toTuple({ ...arbitrary, scheme })).toEqual([
194
+ '0x',
195
+ '0x',
196
+ '0x',
197
+ '0xaabb',
198
+ ])
199
+ },
200
+ )
201
+
202
+ test.each([1, 'secp256k1'] as const)(
203
+ 'encodes secp256k1 scheme %s',
204
+ (scheme) => {
205
+ const entry = FrameSignature.from({
206
+ scheme,
207
+ signature: { r: '0x01', s: '0x02', yParity: 1 },
208
+ })
209
+ expect(FrameSignature.toTuple(entry)).toEqual([
210
+ '0x01',
211
+ '0x',
212
+ '0x',
213
+ secpBytes,
214
+ ])
215
+ },
216
+ )
217
+
218
+ test.each([2, 'p256'] as const)('encodes P-256 scheme %s', (scheme) => {
219
+ const entry = FrameSignature.from({
220
+ publicKey,
221
+ scheme,
222
+ signature: { r: '0x01', s: '0x02' },
223
+ })
224
+ expect(FrameSignature.toTuple(entry)).toEqual([
225
+ '0x02',
226
+ '0x',
227
+ '0x',
228
+ p256Bytes,
229
+ ])
230
+ })
231
+
232
+ test('retains explicit payload and signer', () => {
233
+ const entry = FrameSignature.from({
234
+ payload,
235
+ scheme: 'secp256k1',
236
+ signer: '0x0000000000000000000000000000000000000000',
237
+ })
238
+ expect(FrameSignature.toTuple(entry)).toEqual([
239
+ '0x01',
240
+ entry.signer,
241
+ payload,
242
+ '0x',
243
+ ])
244
+ expect(FrameSignature.fromTuple(FrameSignature.toTuple(entry))).toEqual(
245
+ entry,
246
+ )
247
+ })
248
+
249
+ test('normalizes P-256 high-s without mutating input', () => {
250
+ const signature = Object.freeze({
251
+ r: '0x01',
252
+ s: Hex.fromNumber(p256.Point.Fn.ORDER - 2n),
253
+ })
254
+ const entry = FrameSignature.from({ publicKey, scheme: 'p256', signature })
255
+ expect(FrameSignature.toTuple(entry)[3]).toBe(p256Bytes)
256
+ expect(entry.signature.s).toBe(Hex.fromNumber(p256.Point.Fn.ORDER - 2n))
257
+ })
258
+ })
259
+
260
+ describe('fromTuple', () => {
261
+ test('decodes fixed arbitrary tuple', () => {
262
+ expect(FrameSignature.fromTuple(['0x', '0x', '0x', '0xaabb'])).toEqual(
263
+ arbitrary,
264
+ )
265
+ })
266
+
267
+ test('decodes fixed secp256k1 bytes', () => {
268
+ expect(FrameSignature.fromTuple(['0x01', '0x', '0x', secpBytes])).toEqual({
269
+ payload: '0x',
270
+ scheme: 'secp256k1',
271
+ signature: {
272
+ r: Hex.fromNumber(1, { size: 32 }),
273
+ s: Hex.fromNumber(2, { size: 32 }),
274
+ yParity: 1,
275
+ },
276
+ })
277
+ })
278
+
279
+ test('decodes fixed P-256 bytes', () => {
280
+ expect(FrameSignature.fromTuple(['0x02', '0x', '0x', p256Bytes])).toEqual({
281
+ payload: '0x',
282
+ publicKey,
283
+ scheme: 'p256',
284
+ signature: {
285
+ r: Hex.fromNumber(1, { size: 32 }),
286
+ s: Hex.fromNumber(2, { size: 32 }),
287
+ },
288
+ })
289
+ })
290
+
291
+ test.each([
292
+ ['0x01', 'secp256k1'],
293
+ ['0x02', 'p256'],
294
+ ] as const)('decodes unsigned %s entries', (scheme, name) => {
295
+ expect(FrameSignature.fromTuple([scheme, '0x', '0x', '0x'])).toEqual({
296
+ payload: '0x',
297
+ scheme: name,
298
+ })
299
+ })
300
+
301
+ test('round-trips a real secp256k1 signature', () => {
302
+ const signature = Secp256k1.sign({
303
+ extraEntropy: false,
304
+ payload,
305
+ privateKey,
306
+ })
307
+ const entry = FrameSignature.fromTuple(
308
+ FrameSignature.toTuple(
309
+ FrameSignature.from({ payload, scheme: 'secp256k1', signature }),
310
+ ),
311
+ ) as FrameSignature.Secp256k1
312
+ expect(entry.signature).toEqual(signature)
313
+ expect(
314
+ Secp256k1.recoverAddress({ payload, signature: entry.signature! }),
315
+ ).toBe('0x7e5f4552091a69125d5dfcb7b8c2659029395bdf')
316
+ })
317
+
318
+ test('round-trips a real P-256 signature', () => {
319
+ const signature = P256.sign({ extraEntropy: false, payload, privateKey })
320
+ const entry = FrameSignature.fromTuple(
321
+ FrameSignature.toTuple(
322
+ FrameSignature.from({ publicKey, scheme: 'p256', signature }),
323
+ ),
324
+ ) as FrameSignature.P256
325
+ expect(
326
+ P256.verify({
327
+ payload,
328
+ publicKey: entry.publicKey!,
329
+ signature: entry.signature!,
330
+ }),
331
+ ).toBe(true)
332
+ })
333
+
334
+ test.each(['0x00', '0x0001', '0x1', '0x03'])(
335
+ 'rejects scheme %s',
336
+ (scheme) => {
337
+ expect(() =>
338
+ FrameSignature.fromTuple([scheme, '0x', '0x', '0x'] as never),
339
+ ).toThrow()
340
+ },
341
+ )
342
+
343
+ test.each([
344
+ [],
345
+ ['0x', '0x', '0x'],
346
+ ['0x', '0x', '0x', '0x', '0x'],
347
+ ['0x01', undefined, '0x', '0x'],
348
+ ['0x', '0x0000000000000000000000000000000000000000', '0x', '0x'],
349
+ ['0x01', '0x', '0x', '0x01'],
350
+ ['0x02', '0x', '0x', '0x01'],
351
+ ['0x01', '0x', '0x', `0x02${secpBytes.slice(4)}`],
352
+ [
353
+ '0x02',
354
+ '0x',
355
+ '0x',
356
+ Hex.concat(
357
+ Hex.slice(p256Bytes, 0, 32),
358
+ Hex.fromNumber(p256.Point.Fn.ORDER - 2n, { size: 32 }),
359
+ Hex.slice(p256Bytes, 64),
360
+ ),
361
+ ],
362
+ ])('rejects malformed tuple %#', (...tuple) => {
363
+ expect(() => FrameSignature.fromTuple(tuple as never)).toThrow()
364
+ })
365
+ })
366
+
367
+ describe('assert', () => {
368
+ test.each([
369
+ FrameSignature.from({ scheme: 1 }),
370
+ FrameSignature.from({ scheme: 2 }),
371
+ FrameSignature.from({ scheme: 'secp256k1' }),
372
+ FrameSignature.from({ scheme: 'p256' }),
373
+ ])('requires signature only when signed for $scheme', (entry) => {
374
+ expect(() => FrameSignature.assert(entry)).not.toThrow()
375
+ expect(() => FrameSignature.assert(entry, { signed: true })).toThrow(
376
+ FrameSignature.InvalidError,
377
+ )
378
+ })
379
+
380
+ test('allows empty arbitrary witness even when signed', () => {
381
+ expect(() =>
382
+ FrameSignature.assert(FrameSignature.from({ signature: '0x' }), {
383
+ signed: true,
384
+ }),
385
+ ).not.toThrow()
386
+ })
387
+
388
+ test.each([
389
+ { scheme: 3 },
390
+ { scheme: -1 },
391
+ { scheme: 1.5 },
392
+ { scheme: 'invalid' },
393
+ { scheme: 'toString' },
394
+ { signer: '0x0000000000000000000000000000000000000000' },
395
+ { payload: '0x00' },
396
+ { payload: `0x${'00'.repeat(32)}` },
397
+ { payload: '0x0' },
398
+ { signature: '0xa' },
399
+ { signature: '0xgg' },
400
+ { scheme: 1, signature: '0x01' },
401
+ { scheme: 2, signature: '0x01' },
402
+ ])('rejects invalid entry %#', (fields) => {
403
+ expect(() =>
404
+ FrameSignature.assert({ ...arbitrary, ...fields } as never),
405
+ ).toThrow()
406
+ })
407
+
408
+ test.each([0n, secp256k1.Point.Fn.ORDER, secp256k1.Point.Fn.ORDER - 1n])(
409
+ 'rejects invalid secp256k1 s %s',
410
+ (s) => {
411
+ expect(() =>
412
+ FrameSignature.from({
413
+ scheme: 1,
414
+ signature: { r: '0x01', s: Hex.fromNumber(s), yParity: 0 },
415
+ }),
416
+ ).toThrow()
417
+ },
418
+ )
419
+
420
+ test.each([0n, p256.Point.Fn.ORDER, p256.Point.Fn.ORDER + 1n])(
421
+ 'rejects invalid P-256 s %s',
422
+ (s) => {
423
+ expect(() =>
424
+ FrameSignature.from({
425
+ publicKey,
426
+ scheme: 2,
427
+ signature: { r: '0x01', s: Hex.fromNumber(s) },
428
+ }),
429
+ ).toThrow()
430
+ },
431
+ )
432
+
433
+ test('rejects missing public key for signed P-256', () => {
434
+ expect(() =>
435
+ FrameSignature.assert({
436
+ payload: '0x',
437
+ scheme: 2,
438
+ signature: { r: '0x01', s: '0x02' },
439
+ } as never),
440
+ ).toThrow()
441
+ })
442
+ })
443
+
444
+ describe('validate', () => {
445
+ test('reports structural validity without signature verification', () => {
446
+ expect(FrameSignature.validate(arbitrary)).toBe(true)
447
+ expect(FrameSignature.validate({ ...arbitrary, payload: '0x01' })).toBe(
448
+ false,
449
+ )
450
+ })
451
+ })
452
+
453
+ describe('fromRpc', () => {
454
+ test('arbitrary signature', () => {
455
+ expect(
456
+ FrameSignature.fromRpc({
457
+ msg: '0x',
458
+ scheme: 0,
459
+ signature: '0xaabb',
460
+ signer: null,
461
+ }),
462
+ ).toEqual(FrameSignature.from('0xaabb'))
463
+ })
464
+ test('secp256k1 signature', () => {
465
+ expect(
466
+ FrameSignature.fromRpc({ msg: '0x', scheme: 1, signature: secpBytes }),
467
+ ).toEqual(
468
+ FrameSignature.from({
469
+ scheme: 'secp256k1',
470
+ signature: {
471
+ r: `0x${'00'.repeat(31)}01`,
472
+ s: `0x${'00'.repeat(31)}02`,
473
+ yParity: 1,
474
+ },
475
+ }),
476
+ )
477
+ })
478
+ test('P-256 signature', () => {
479
+ const entry = FrameSignature.fromRpc({
480
+ msg: '0x',
481
+ scheme: 2,
482
+ signature: p256Bytes,
483
+ })
484
+ expect(entry).toMatchObject({ payload: '0x', publicKey, scheme: 'p256' })
485
+ expect(FrameSignature.toRpc(entry)).toEqual({
486
+ msg: '0x',
487
+ scheme: 2,
488
+ signature: p256Bytes,
489
+ })
490
+ })
491
+ test('unsigned placeholder', () => {
492
+ expect(
493
+ FrameSignature.fromRpc({ msg: '0x', scheme: 1, signature: '0x' }),
494
+ ).toEqual(FrameSignature.from({ scheme: 'secp256k1' }))
495
+ })
496
+ test('malformed protocol signature', () => {
497
+ expect(() =>
498
+ FrameSignature.fromRpc({ msg: '0x', scheme: 1, signature: '0x01' }),
499
+ ).toThrowErrorMatchingInlineSnapshot(
500
+ `[FrameSignature.InvalidError: Invalid frame signature.
501
+
502
+ Details: Invalid protocol signature length.]`,
503
+ )
504
+ })
505
+ })
506
+
507
+ describe('toRpc', () => {
508
+ test('preserves explicit payload and signer', () => {
509
+ const entry = FrameSignature.from({
510
+ payload,
511
+ scheme: 'secp256k1',
512
+ signature: { r: '0x01', s: '0x02', yParity: 1 },
513
+ signer: '0x1111111111111111111111111111111111111111',
514
+ })
515
+ expect(FrameSignature.toRpc(entry)).toEqual({
516
+ msg: payload,
517
+ scheme: 1,
518
+ signature: secpBytes,
519
+ signer: '0x1111111111111111111111111111111111111111',
520
+ })
521
+ expect(entry.signature.r).toBe('0x01')
522
+ })
523
+ })
524
+
525
+ describe('structured validation', () => {
526
+ test.each(['invalid', '1', '0x', `0x${'11'.repeat(33)}`])(
527
+ 'rejects coordinate %s',
528
+ (coordinate) => {
529
+ for (const key of ['x', 'y'] as const) {
530
+ const entry = {
531
+ payload: '0x',
532
+ publicKey: { ...publicKey, [key]: coordinate },
533
+ scheme: 'p256',
534
+ } as FrameSignature.P256
535
+ expect(FrameSignature.validate(entry)).toBe(false)
536
+ expect(() => FrameSignature.from(entry)).toThrow()
537
+ }
538
+ },
539
+ )
540
+ test.each([
541
+ { r: '1', s: '0x02', yParity: 1 },
542
+ { r: '0x01', s: '2', yParity: 1 },
543
+ { r: '0x01', s: '0x02', yParity: '1' },
544
+ { r: '0x01', s: '0x02', yParity: undefined },
545
+ ])('rejects malformed scalars or parity %j', (signature) => {
546
+ const entry = {
547
+ payload: '0x',
548
+ scheme: 'secp256k1',
549
+ signature,
550
+ } as unknown as FrameSignature.Secp256k1
551
+ expect(FrameSignature.validate(entry)).toBe(false)
552
+ expect(() => FrameSignature.from(entry)).toThrow()
553
+ })
554
+ })