tempo.ts 0.0.6 → 0.1.1

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 (195) hide show
  1. package/dist/chains.d.ts +244 -541
  2. package/dist/chains.d.ts.map +1 -1
  3. package/dist/chains.js +10 -23
  4. package/dist/chains.js.map +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/ox/SignatureEnvelope.d.ts +245 -0
  7. package/dist/ox/SignatureEnvelope.d.ts.map +1 -0
  8. package/dist/ox/SignatureEnvelope.js +437 -0
  9. package/dist/ox/SignatureEnvelope.js.map +1 -0
  10. package/dist/ox/Transaction.d.ts +61 -24
  11. package/dist/ox/Transaction.d.ts.map +1 -1
  12. package/dist/ox/Transaction.js +63 -18
  13. package/dist/ox/Transaction.js.map +1 -1
  14. package/dist/ox/TransactionEnvelopeAA.d.ts +461 -0
  15. package/dist/ox/TransactionEnvelopeAA.d.ts.map +1 -0
  16. package/dist/ox/TransactionEnvelopeAA.js +528 -0
  17. package/dist/ox/TransactionEnvelopeAA.js.map +1 -0
  18. package/dist/ox/TransactionRequest.d.ts +7 -5
  19. package/dist/ox/TransactionRequest.d.ts.map +1 -1
  20. package/dist/ox/TransactionRequest.js +21 -12
  21. package/dist/ox/TransactionRequest.js.map +1 -1
  22. package/dist/ox/index.d.ts +5 -4
  23. package/dist/ox/index.d.ts.map +1 -1
  24. package/dist/ox/index.js +5 -4
  25. package/dist/ox/index.js.map +1 -1
  26. package/dist/prool/Instance.d.ts +0 -4
  27. package/dist/prool/Instance.d.ts.map +1 -1
  28. package/dist/prool/Instance.js +7 -7
  29. package/dist/prool/Instance.js.map +1 -1
  30. package/dist/prool/index.d.ts +1 -1
  31. package/dist/prool/index.d.ts.map +1 -1
  32. package/dist/prool/index.js +1 -1
  33. package/dist/prool/index.js.map +1 -1
  34. package/dist/viem/{abis.d.ts → Abis.d.ts} +523 -9
  35. package/dist/viem/Abis.d.ts.map +1 -0
  36. package/dist/viem/{abis.js → Abis.js} +321 -9
  37. package/dist/viem/Abis.js.map +1 -0
  38. package/dist/viem/{actions → Actions}/amm.d.ts +21 -21
  39. package/dist/viem/Actions/amm.d.ts.map +1 -0
  40. package/dist/viem/{actions → Actions}/amm.js +55 -43
  41. package/dist/viem/Actions/amm.js.map +1 -0
  42. package/dist/viem/Actions/dex.d.ts +3263 -0
  43. package/dist/viem/Actions/dex.d.ts.map +1 -0
  44. package/dist/viem/Actions/dex.js +1357 -0
  45. package/dist/viem/Actions/dex.js.map +1 -0
  46. package/dist/viem/{actions → Actions}/fee.d.ts +8 -8
  47. package/dist/viem/Actions/fee.d.ts.map +1 -0
  48. package/dist/viem/{actions → Actions}/fee.js +14 -13
  49. package/dist/viem/Actions/fee.js.map +1 -0
  50. package/dist/viem/Actions/index.d.ts +6 -0
  51. package/dist/viem/Actions/index.d.ts.map +1 -0
  52. package/dist/viem/Actions/index.js +6 -0
  53. package/dist/viem/Actions/index.js.map +1 -0
  54. package/dist/viem/{actions → Actions}/policy.d.ts +19 -19
  55. package/dist/viem/Actions/policy.d.ts.map +1 -0
  56. package/dist/viem/{actions → Actions}/policy.js +59 -46
  57. package/dist/viem/Actions/policy.js.map +1 -0
  58. package/dist/viem/{actions → Actions}/token.d.ts +3275 -698
  59. package/dist/viem/Actions/token.d.ts.map +1 -0
  60. package/dist/viem/{actions → Actions}/token.js +458 -84
  61. package/dist/viem/Actions/token.js.map +1 -0
  62. package/dist/viem/Addresses.d.ts +9 -0
  63. package/dist/viem/Addresses.d.ts.map +1 -0
  64. package/dist/viem/Addresses.js +9 -0
  65. package/dist/viem/Addresses.js.map +1 -0
  66. package/dist/viem/{chain.d.ts → Chain.d.ts} +81 -57
  67. package/dist/viem/Chain.d.ts.map +1 -0
  68. package/dist/viem/{chain.js → Chain.js} +7 -7
  69. package/dist/viem/Chain.js.map +1 -0
  70. package/dist/viem/{client.d.ts → Client.d.ts} +4 -4
  71. package/dist/viem/Client.d.ts.map +1 -0
  72. package/dist/viem/{client.js → Client.js} +3 -3
  73. package/dist/viem/Client.js.map +1 -0
  74. package/dist/viem/{decorator.d.ts → Decorator.d.ts} +507 -5
  75. package/dist/viem/Decorator.d.ts.map +1 -0
  76. package/dist/viem/{decorator.js → Decorator.js} +31 -5
  77. package/dist/viem/Decorator.js.map +1 -0
  78. package/dist/viem/{formatters.d.ts → Formatters.d.ts} +2 -2
  79. package/dist/viem/Formatters.d.ts.map +1 -0
  80. package/dist/viem/{formatters.js → Formatters.js} +24 -17
  81. package/dist/viem/Formatters.js.map +1 -0
  82. package/dist/viem/Tick.d.ts +111 -0
  83. package/dist/viem/Tick.d.ts.map +1 -0
  84. package/dist/viem/Tick.js +127 -0
  85. package/dist/viem/Tick.js.map +1 -0
  86. package/dist/viem/TokenIds.d.ts +3 -0
  87. package/dist/viem/TokenIds.d.ts.map +1 -0
  88. package/dist/viem/TokenIds.js +3 -0
  89. package/dist/viem/TokenIds.js.map +1 -0
  90. package/dist/viem/Transaction.d.ts +57 -0
  91. package/dist/viem/Transaction.d.ts.map +1 -0
  92. package/dist/viem/Transaction.js +137 -0
  93. package/dist/viem/Transaction.js.map +1 -0
  94. package/dist/viem/{transport.d.ts → Transport.d.ts} +3 -3
  95. package/dist/viem/Transport.d.ts.map +1 -0
  96. package/dist/viem/{transport.js → Transport.js} +3 -3
  97. package/dist/viem/Transport.js.map +1 -0
  98. package/dist/viem/index.d.ts +13 -9
  99. package/dist/viem/index.d.ts.map +1 -1
  100. package/dist/viem/index.js +13 -9
  101. package/dist/viem/index.js.map +1 -1
  102. package/dist/viem/{types.d.ts → internal/types.d.ts} +3 -3
  103. package/dist/viem/internal/types.d.ts.map +1 -0
  104. package/dist/viem/{types.js.map → internal/types.js.map} +1 -1
  105. package/dist/viem/internal/utils.d.ts.map +1 -0
  106. package/dist/viem/internal/utils.js.map +1 -0
  107. package/package.json +87 -101
  108. package/src/chains.ts +10 -24
  109. package/src/ox/SignatureEnvelope.test.ts +1252 -0
  110. package/src/ox/SignatureEnvelope.ts +709 -0
  111. package/src/ox/Transaction.test.ts +144 -89
  112. package/src/ox/Transaction.ts +104 -29
  113. package/src/ox/TransactionEnvelopeAA.test.ts +1533 -0
  114. package/src/ox/TransactionEnvelopeAA.ts +858 -0
  115. package/src/ox/TransactionRequest.ts +25 -17
  116. package/src/ox/index.ts +2 -1
  117. package/src/prool/Instance.ts +6 -14
  118. package/src/prool/internal/chain.json +101 -27
  119. package/src/viem/{abis.ts → Abis.ts} +322 -8
  120. package/src/viem/{actions → Actions}/amm.test.ts +65 -68
  121. package/src/viem/{actions → Actions}/amm.ts +72 -60
  122. package/src/viem/Actions/dex.test.ts +1608 -0
  123. package/src/viem/Actions/dex.ts +2026 -0
  124. package/src/viem/{actions → Actions}/fee.test.ts +34 -36
  125. package/src/viem/{actions → Actions}/fee.ts +18 -17
  126. package/src/viem/{actions → Actions}/index.ts +1 -0
  127. package/src/viem/{actions → Actions}/policy.test.ts +2 -2
  128. package/src/viem/{actions → Actions}/policy.ts +77 -64
  129. package/src/viem/{actions → Actions}/token.test.ts +419 -64
  130. package/src/viem/{actions → Actions}/token.ts +751 -145
  131. package/src/viem/Addresses.ts +9 -0
  132. package/src/viem/{chain.ts → Chain.ts} +6 -6
  133. package/src/viem/{client.bench-d.ts → Client.bench-d.ts} +2 -2
  134. package/src/viem/{client.test.ts → Client.test.ts} +31 -6
  135. package/src/viem/{client.ts → Client.ts} +1 -1
  136. package/src/viem/{decorator.bench-d.ts → Decorator.bench-d.ts} +2 -2
  137. package/src/viem/{decorator.test.ts → Decorator.test.ts} +1 -0
  138. package/src/viem/{decorator.ts → Decorator.ts} +586 -4
  139. package/src/viem/{formatters.ts → Formatters.ts} +31 -20
  140. package/src/viem/Tick.test.ts +281 -0
  141. package/src/viem/Tick.ts +176 -0
  142. package/src/viem/TokenIds.ts +2 -0
  143. package/src/viem/Transaction.ts +303 -0
  144. package/src/viem/{transport.ts → Transport.ts} +5 -5
  145. package/src/viem/e2e.test.ts +153 -78
  146. package/src/viem/index.ts +13 -9
  147. package/src/viem/{types.ts → internal/types.ts} +3 -3
  148. package/dist/ox/TransactionEnvelopeFeeToken.d.ts +0 -393
  149. package/dist/ox/TransactionEnvelopeFeeToken.d.ts.map +0 -1
  150. package/dist/ox/TransactionEnvelopeFeeToken.js +0 -452
  151. package/dist/ox/TransactionEnvelopeFeeToken.js.map +0 -1
  152. package/dist/viem/abis.d.ts.map +0 -1
  153. package/dist/viem/abis.js.map +0 -1
  154. package/dist/viem/actions/amm.d.ts.map +0 -1
  155. package/dist/viem/actions/amm.js.map +0 -1
  156. package/dist/viem/actions/fee.d.ts.map +0 -1
  157. package/dist/viem/actions/fee.js.map +0 -1
  158. package/dist/viem/actions/index.d.ts +0 -5
  159. package/dist/viem/actions/index.d.ts.map +0 -1
  160. package/dist/viem/actions/index.js +0 -5
  161. package/dist/viem/actions/index.js.map +0 -1
  162. package/dist/viem/actions/policy.d.ts.map +0 -1
  163. package/dist/viem/actions/policy.js.map +0 -1
  164. package/dist/viem/actions/token.d.ts.map +0 -1
  165. package/dist/viem/actions/token.js.map +0 -1
  166. package/dist/viem/addresses.d.ts +0 -8
  167. package/dist/viem/addresses.d.ts.map +0 -1
  168. package/dist/viem/addresses.js +0 -8
  169. package/dist/viem/addresses.js.map +0 -1
  170. package/dist/viem/chain.d.ts.map +0 -1
  171. package/dist/viem/chain.js.map +0 -1
  172. package/dist/viem/client.d.ts.map +0 -1
  173. package/dist/viem/client.js.map +0 -1
  174. package/dist/viem/decorator.d.ts.map +0 -1
  175. package/dist/viem/decorator.js.map +0 -1
  176. package/dist/viem/formatters.d.ts.map +0 -1
  177. package/dist/viem/formatters.js.map +0 -1
  178. package/dist/viem/transaction.d.ts +0 -54
  179. package/dist/viem/transaction.d.ts.map +0 -1
  180. package/dist/viem/transaction.js +0 -108
  181. package/dist/viem/transaction.js.map +0 -1
  182. package/dist/viem/transport.d.ts.map +0 -1
  183. package/dist/viem/transport.js.map +0 -1
  184. package/dist/viem/types.d.ts.map +0 -1
  185. package/dist/viem/utils.d.ts.map +0 -1
  186. package/dist/viem/utils.js.map +0 -1
  187. package/src/ox/TransactionEnvelopeFeeToken.test.ts +0 -1119
  188. package/src/ox/TransactionEnvelopeFeeToken.ts +0 -717
  189. package/src/prool/internal/consensus.toml +0 -32
  190. package/src/viem/addresses.ts +0 -10
  191. package/src/viem/transaction.ts +0 -253
  192. /package/dist/viem/{types.js → internal/types.js} +0 -0
  193. /package/dist/viem/{utils.d.ts → internal/utils.d.ts} +0 -0
  194. /package/dist/viem/{utils.js → internal/utils.js} +0 -0
  195. /package/src/viem/{utils.ts → internal/utils.ts} +0 -0
@@ -0,0 +1,1252 @@
1
+ import { Hex, PublicKey, Secp256k1, Signature, WebAuthnP256 } from 'ox'
2
+ import { describe, expect, test } from 'vitest'
3
+ import * as SignatureEnvelope from './SignatureEnvelope.js'
4
+
5
+ const publicKey = PublicKey.from({
6
+ prefix: 4,
7
+ x: 78495282704852028275327922540131762143565388050940484317945369745559774511861n,
8
+ y: 8109764566587999957624872393871720746996669263962991155166704261108473113504n,
9
+ })
10
+
11
+ const p256Signature = Signature.from({
12
+ r: 92602584010956101470289867944347135737570451066466093224269890121909314569518n,
13
+ s: 54171125190222965779385658110416711469231271457324878825831748147306957269813n,
14
+ yParity: 0,
15
+ })
16
+
17
+ const signature_secp256k1 = Secp256k1.sign({
18
+ payload: '0xdeadbeef',
19
+ privateKey: Secp256k1.randomPrivateKey(),
20
+ })
21
+
22
+ const signature_p256 = SignatureEnvelope.from({
23
+ signature: p256Signature,
24
+ publicKey,
25
+ prehash: true,
26
+ })
27
+
28
+ const signature_webauthn = SignatureEnvelope.from({
29
+ signature: p256Signature,
30
+ publicKey,
31
+ metadata: {
32
+ authenticatorData: WebAuthnP256.getAuthenticatorData({ rpId: 'localhost' }),
33
+ clientDataJSON: WebAuthnP256.getClientDataJSON({
34
+ challenge: '0xdeadbeef',
35
+ origin: 'http://localhost',
36
+ }),
37
+ },
38
+ })
39
+
40
+ describe('assert', () => {
41
+ describe('secp256k1', () => {
42
+ test('behavior: validates valid signature', () => {
43
+ expect(() =>
44
+ SignatureEnvelope.assert({
45
+ signature: signature_secp256k1,
46
+ type: 'secp256k1',
47
+ }),
48
+ ).not.toThrow()
49
+ })
50
+
51
+ test('behavior: validates signature without explicit type', () => {
52
+ expect(() =>
53
+ SignatureEnvelope.assert({ signature: signature_secp256k1 }),
54
+ ).not.toThrow()
55
+ })
56
+
57
+ test('error: throws on invalid signature values', () => {
58
+ expect(() =>
59
+ SignatureEnvelope.assert({
60
+ signature: {
61
+ r: 0n,
62
+ s: 0n,
63
+ yParity: 2,
64
+ },
65
+ type: 'secp256k1',
66
+ }),
67
+ ).toThrowErrorMatchingInlineSnapshot(
68
+ `[Signature.InvalidYParityError: Value \`2\` is an invalid y-parity value. Y-parity must be 0 or 1.]`,
69
+ )
70
+ })
71
+ })
72
+
73
+ describe('p256', () => {
74
+ test('behavior: validates valid P256 signature', () => {
75
+ expect(() => SignatureEnvelope.assert(signature_p256)).not.toThrow()
76
+ })
77
+
78
+ test('behavior: validates P256 signature without explicit type', () => {
79
+ const { type: _, ...signatureWithoutType } = signature_p256
80
+ expect(() => SignatureEnvelope.assert(signatureWithoutType)).not.toThrow()
81
+ })
82
+
83
+ test('error: throws on invalid prehash type', () => {
84
+ expect(() =>
85
+ SignatureEnvelope.assert({
86
+ ...signature_p256,
87
+ prehash: 'true' as any,
88
+ }),
89
+ ).toThrowErrorMatchingInlineSnapshot(
90
+ `
91
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "p256" is missing required properties: \`prehash\`.
92
+
93
+ Provided: {"signature":{"r":"92602584010956101470289867944347135737570451066466093224269890121909314569518#__bigint","s":"54171125190222965779385658110416711469231271457324878825831748147306957269813#__bigint","yParity":0},"publicKey":{"prefix":4,"x":"78495282704852028275327922540131762143565388050940484317945369745559774511861#__bigint","y":"8109764566587999957624872393871720746996669263962991155166704261108473113504#__bigint"},"prehash":"true","type":"p256"}]
94
+ `,
95
+ )
96
+ })
97
+
98
+ test('error: throws on missing publicKey', () => {
99
+ const { publicKey: _, ...withoutPublicKey } = signature_p256
100
+ expect(() =>
101
+ SignatureEnvelope.assert(withoutPublicKey as any),
102
+ ).toThrowErrorMatchingInlineSnapshot(
103
+ `
104
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "p256" is missing required properties: \`publicKey\`.
105
+
106
+ Provided: {"signature":{"r":"92602584010956101470289867944347135737570451066466093224269890121909314569518#__bigint","s":"54171125190222965779385658110416711469231271457324878825831748147306957269813#__bigint","yParity":0},"prehash":true,"type":"p256"}]
107
+ `,
108
+ )
109
+ })
110
+
111
+ test('error: throws on missing signature.r', () => {
112
+ const invalid = {
113
+ signature: { s: 1n } as any,
114
+ publicKey,
115
+ prehash: true,
116
+ type: 'p256' as const,
117
+ }
118
+ expect(() =>
119
+ SignatureEnvelope.assert(invalid),
120
+ ).toThrowErrorMatchingInlineSnapshot(
121
+ `
122
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "p256" is missing required properties: \`signature.r\`.
123
+
124
+ Provided: {"signature":{"s":"1#__bigint"},"publicKey":{"prefix":4,"x":"78495282704852028275327922540131762143565388050940484317945369745559774511861#__bigint","y":"8109764566587999957624872393871720746996669263962991155166704261108473113504#__bigint"},"prehash":true,"type":"p256"}]
125
+ `,
126
+ )
127
+ })
128
+
129
+ test('error: throws on missing signature.s', () => {
130
+ const invalid = {
131
+ signature: { r: 1n } as any,
132
+ publicKey,
133
+ prehash: true,
134
+ type: 'p256' as const,
135
+ }
136
+ expect(() =>
137
+ SignatureEnvelope.assert(invalid),
138
+ ).toThrowErrorMatchingInlineSnapshot(
139
+ `
140
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "p256" is missing required properties: \`signature.s\`.
141
+
142
+ Provided: {"signature":{"r":"1#__bigint"},"publicKey":{"prefix":4,"x":"78495282704852028275327922540131762143565388050940484317945369745559774511861#__bigint","y":"8109764566587999957624872393871720746996669263962991155166704261108473113504#__bigint"},"prehash":true,"type":"p256"}]
143
+ `,
144
+ )
145
+ })
146
+
147
+ test('error: throws on missing publicKey.x', () => {
148
+ const invalid = {
149
+ signature: p256Signature,
150
+ publicKey: { y: 1n } as any,
151
+ prehash: true,
152
+ type: 'p256' as const,
153
+ }
154
+ expect(() =>
155
+ SignatureEnvelope.assert(invalid),
156
+ ).toThrowErrorMatchingInlineSnapshot(
157
+ `
158
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "p256" is missing required properties: \`publicKey.x\`.
159
+
160
+ Provided: {"signature":{"r":"92602584010956101470289867944347135737570451066466093224269890121909314569518#__bigint","s":"54171125190222965779385658110416711469231271457324878825831748147306957269813#__bigint","yParity":0},"publicKey":{"y":"1#__bigint"},"prehash":true,"type":"p256"}]
161
+ `,
162
+ )
163
+ })
164
+
165
+ test('error: throws on missing publicKey.y', () => {
166
+ const invalid = {
167
+ signature: p256Signature,
168
+ publicKey: { x: 1n } as any,
169
+ prehash: true,
170
+ type: 'p256' as const,
171
+ }
172
+ expect(() =>
173
+ SignatureEnvelope.assert(invalid),
174
+ ).toThrowErrorMatchingInlineSnapshot(
175
+ `
176
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "p256" is missing required properties: \`publicKey.y\`.
177
+
178
+ Provided: {"signature":{"r":"92602584010956101470289867944347135737570451066466093224269890121909314569518#__bigint","s":"54171125190222965779385658110416711469231271457324878825831748147306957269813#__bigint","yParity":0},"publicKey":{"x":"1#__bigint"},"prehash":true,"type":"p256"}]
179
+ `,
180
+ )
181
+ })
182
+
183
+ test('error: throws with all missing properties listed', () => {
184
+ const invalid = {
185
+ signature: {} as any,
186
+ type: 'p256' as const,
187
+ }
188
+ expect(() =>
189
+ SignatureEnvelope.assert(invalid),
190
+ ).toThrowErrorMatchingInlineSnapshot(
191
+ `
192
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "p256" is missing required properties: \`signature.r\`, \`signature.s\`, \`prehash\`, \`publicKey\`.
193
+
194
+ Provided: {"signature":{},"type":"p256"}]
195
+ `,
196
+ )
197
+ })
198
+ })
199
+
200
+ describe('webAuthn', () => {
201
+ test('behavior: validates valid WebAuthn signature', () => {
202
+ expect(() => SignatureEnvelope.assert(signature_webauthn)).not.toThrow()
203
+ })
204
+
205
+ test('behavior: validates WebAuthn signature without explicit type', () => {
206
+ const { type: _, ...signatureWithoutType } = signature_webauthn
207
+ expect(() => SignatureEnvelope.assert(signatureWithoutType)).not.toThrow()
208
+ })
209
+
210
+ test('error: throws on missing metadata', () => {
211
+ const { metadata: _, ...withoutMetadata } = signature_webauthn
212
+ expect(() =>
213
+ SignatureEnvelope.assert(withoutMetadata as any),
214
+ ).toThrowErrorMatchingInlineSnapshot(
215
+ `
216
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "webAuthn" is missing required properties: \`metadata\`.
217
+
218
+ Provided: {"signature":{"r":"92602584010956101470289867944347135737570451066466093224269890121909314569518#__bigint","s":"54171125190222965779385658110416711469231271457324878825831748147306957269813#__bigint","yParity":0},"publicKey":{"prefix":4,"x":"78495282704852028275327922540131762143565388050940484317945369745559774511861#__bigint","y":"8109764566587999957624872393871720746996669263962991155166704261108473113504#__bigint"},"type":"webAuthn"}]
219
+ `,
220
+ )
221
+ })
222
+
223
+ test('error: throws on missing publicKey', () => {
224
+ const { publicKey: _, ...withoutPublicKey } = signature_webauthn
225
+ expect(() =>
226
+ SignatureEnvelope.assert(withoutPublicKey as any),
227
+ ).toThrowErrorMatchingInlineSnapshot(
228
+ `
229
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "webAuthn" is missing required properties: \`publicKey\`.
230
+
231
+ Provided: {"signature":{"r":"92602584010956101470289867944347135737570451066466093224269890121909314569518#__bigint","s":"54171125190222965779385658110416711469231271457324878825831748147306957269813#__bigint","yParity":0},"metadata":{"authenticatorData":"0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000","clientDataJSON":"{\\"type\\":\\"webauthn.get\\",\\"challenge\\":\\"3q2-7w\\",\\"origin\\":\\"http://localhost\\",\\"crossOrigin\\":false}"},"type":"webAuthn"}]
232
+ `,
233
+ )
234
+ })
235
+
236
+ test('error: throws on missing signature.r', () => {
237
+ const invalid = {
238
+ signature: { s: 1n } as any,
239
+ publicKey,
240
+ metadata: {
241
+ authenticatorData: WebAuthnP256.getAuthenticatorData({
242
+ rpId: 'localhost',
243
+ }),
244
+ clientDataJSON: WebAuthnP256.getClientDataJSON({
245
+ challenge: '0xdeadbeef',
246
+ origin: 'http://localhost',
247
+ }),
248
+ },
249
+ type: 'webAuthn' as const,
250
+ }
251
+ expect(() =>
252
+ SignatureEnvelope.assert(invalid),
253
+ ).toThrowErrorMatchingInlineSnapshot(
254
+ `
255
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "webAuthn" is missing required properties: \`signature.r\`.
256
+
257
+ Provided: {"signature":{"s":"1#__bigint"},"publicKey":{"prefix":4,"x":"78495282704852028275327922540131762143565388050940484317945369745559774511861#__bigint","y":"8109764566587999957624872393871720746996669263962991155166704261108473113504#__bigint"},"metadata":{"authenticatorData":"0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000","clientDataJSON":"{\\"type\\":\\"webauthn.get\\",\\"challenge\\":\\"3q2-7w\\",\\"origin\\":\\"http://localhost\\",\\"crossOrigin\\":false}"},"type":"webAuthn"}]
258
+ `,
259
+ )
260
+ })
261
+
262
+ test('error: throws on missing signature.s', () => {
263
+ const invalid = {
264
+ signature: { r: 1n } as any,
265
+ publicKey,
266
+ metadata: {
267
+ authenticatorData: WebAuthnP256.getAuthenticatorData({
268
+ rpId: 'localhost',
269
+ }),
270
+ clientDataJSON: WebAuthnP256.getClientDataJSON({
271
+ challenge: '0xdeadbeef',
272
+ origin: 'http://localhost',
273
+ }),
274
+ },
275
+ type: 'webAuthn' as const,
276
+ }
277
+ expect(() =>
278
+ SignatureEnvelope.assert(invalid),
279
+ ).toThrowErrorMatchingInlineSnapshot(
280
+ `
281
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "webAuthn" is missing required properties: \`signature.s\`.
282
+
283
+ Provided: {"signature":{"r":"1#__bigint"},"publicKey":{"prefix":4,"x":"78495282704852028275327922540131762143565388050940484317945369745559774511861#__bigint","y":"8109764566587999957624872393871720746996669263962991155166704261108473113504#__bigint"},"metadata":{"authenticatorData":"0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000","clientDataJSON":"{\\"type\\":\\"webauthn.get\\",\\"challenge\\":\\"3q2-7w\\",\\"origin\\":\\"http://localhost\\",\\"crossOrigin\\":false}"},"type":"webAuthn"}]
284
+ `,
285
+ )
286
+ })
287
+
288
+ test('error: throws on missing metadata.authenticatorData', () => {
289
+ const invalid = {
290
+ signature: p256Signature,
291
+ publicKey,
292
+ metadata: {
293
+ clientDataJSON: WebAuthnP256.getClientDataJSON({
294
+ challenge: '0xdeadbeef',
295
+ origin: 'http://localhost',
296
+ }),
297
+ } as any,
298
+ type: 'webAuthn' as const,
299
+ }
300
+ expect(() =>
301
+ SignatureEnvelope.assert(invalid),
302
+ ).toThrowErrorMatchingInlineSnapshot(
303
+ `
304
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "webAuthn" is missing required properties: \`metadata.authenticatorData\`.
305
+
306
+ Provided: {"signature":{"r":"92602584010956101470289867944347135737570451066466093224269890121909314569518#__bigint","s":"54171125190222965779385658110416711469231271457324878825831748147306957269813#__bigint","yParity":0},"publicKey":{"prefix":4,"x":"78495282704852028275327922540131762143565388050940484317945369745559774511861#__bigint","y":"8109764566587999957624872393871720746996669263962991155166704261108473113504#__bigint"},"metadata":{"clientDataJSON":"{\\"type\\":\\"webauthn.get\\",\\"challenge\\":\\"3q2-7w\\",\\"origin\\":\\"http://localhost\\",\\"crossOrigin\\":false}"},"type":"webAuthn"}]
307
+ `,
308
+ )
309
+ })
310
+
311
+ test('error: throws on missing metadata.clientDataJSON', () => {
312
+ const invalid = {
313
+ signature: p256Signature,
314
+ publicKey,
315
+ metadata: {
316
+ authenticatorData: WebAuthnP256.getAuthenticatorData({
317
+ rpId: 'localhost',
318
+ }),
319
+ } as any,
320
+ type: 'webAuthn' as const,
321
+ }
322
+ expect(() =>
323
+ SignatureEnvelope.assert(invalid),
324
+ ).toThrowErrorMatchingInlineSnapshot(
325
+ `
326
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "webAuthn" is missing required properties: \`metadata.clientDataJSON\`.
327
+
328
+ Provided: {"signature":{"r":"92602584010956101470289867944347135737570451066466093224269890121909314569518#__bigint","s":"54171125190222965779385658110416711469231271457324878825831748147306957269813#__bigint","yParity":0},"publicKey":{"prefix":4,"x":"78495282704852028275327922540131762143565388050940484317945369745559774511861#__bigint","y":"8109764566587999957624872393871720746996669263962991155166704261108473113504#__bigint"},"metadata":{"authenticatorData":"0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000"},"type":"webAuthn"}]
329
+ `,
330
+ )
331
+ })
332
+
333
+ test('error: throws on missing publicKey.x', () => {
334
+ const invalid = {
335
+ signature: p256Signature,
336
+ publicKey: { y: 1n } as any,
337
+ metadata: {
338
+ authenticatorData: WebAuthnP256.getAuthenticatorData({
339
+ rpId: 'localhost',
340
+ }),
341
+ clientDataJSON: WebAuthnP256.getClientDataJSON({
342
+ challenge: '0xdeadbeef',
343
+ origin: 'http://localhost',
344
+ }),
345
+ },
346
+ type: 'webAuthn' as const,
347
+ }
348
+ expect(() =>
349
+ SignatureEnvelope.assert(invalid),
350
+ ).toThrowErrorMatchingInlineSnapshot(
351
+ `
352
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "webAuthn" is missing required properties: \`publicKey.x\`.
353
+
354
+ Provided: {"signature":{"r":"92602584010956101470289867944347135737570451066466093224269890121909314569518#__bigint","s":"54171125190222965779385658110416711469231271457324878825831748147306957269813#__bigint","yParity":0},"publicKey":{"y":"1#__bigint"},"metadata":{"authenticatorData":"0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000","clientDataJSON":"{\\"type\\":\\"webauthn.get\\",\\"challenge\\":\\"3q2-7w\\",\\"origin\\":\\"http://localhost\\",\\"crossOrigin\\":false}"},"type":"webAuthn"}]
355
+ `,
356
+ )
357
+ })
358
+
359
+ test('error: throws on missing publicKey.y', () => {
360
+ const invalid = {
361
+ signature: p256Signature,
362
+ publicKey: { x: 1n } as any,
363
+ metadata: {
364
+ authenticatorData: WebAuthnP256.getAuthenticatorData({
365
+ rpId: 'localhost',
366
+ }),
367
+ clientDataJSON: WebAuthnP256.getClientDataJSON({
368
+ challenge: '0xdeadbeef',
369
+ origin: 'http://localhost',
370
+ }),
371
+ },
372
+ type: 'webAuthn' as const,
373
+ }
374
+ expect(() =>
375
+ SignatureEnvelope.assert(invalid),
376
+ ).toThrowErrorMatchingInlineSnapshot(
377
+ `
378
+ [SignatureEnvelope.MissingPropertiesError: Signature envelope of type "webAuthn" is missing required properties: \`publicKey.y\`.
379
+
380
+ Provided: {"signature":{"r":"92602584010956101470289867944347135737570451066466093224269890121909314569518#__bigint","s":"54171125190222965779385658110416711469231271457324878825831748147306957269813#__bigint","yParity":0},"publicKey":{"x":"1#__bigint"},"metadata":{"authenticatorData":"0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000","clientDataJSON":"{\\"type\\":\\"webauthn.get\\",\\"challenge\\":\\"3q2-7w\\",\\"origin\\":\\"http://localhost\\",\\"crossOrigin\\":false}"},"type":"webAuthn"}]
381
+ `,
382
+ )
383
+ })
384
+ })
385
+
386
+ test('error: throws on invalid envelope', () => {
387
+ expect(() =>
388
+ SignatureEnvelope.assert({} as any),
389
+ ).toThrowErrorMatchingInlineSnapshot(
390
+ `[SignatureEnvelope.CoercionError: Unable to coerce value (\`{}\`) to a valid signature envelope.]`,
391
+ )
392
+ })
393
+
394
+ test('error: throws on incomplete signature', () => {
395
+ expect(() =>
396
+ SignatureEnvelope.assert({
397
+ r: 0n,
398
+ s: 0n,
399
+ } as any),
400
+ ).toThrowErrorMatchingInlineSnapshot(
401
+ `[SignatureEnvelope.CoercionError: Unable to coerce value (\`{"r":"0#__bigint","s":"0#__bigint"}\`) to a valid signature envelope.]`,
402
+ )
403
+ })
404
+ })
405
+
406
+ describe('deserialize', () => {
407
+ describe('secp256k1', () => {
408
+ test('behavior: deserializes valid signature', () => {
409
+ const serialized = Signature.toHex(signature_secp256k1)
410
+
411
+ const envelope = SignatureEnvelope.deserialize(serialized)
412
+
413
+ expect(envelope).toMatchObject({
414
+ signature: {
415
+ r: signature_secp256k1.r,
416
+ s: signature_secp256k1.s,
417
+ yParity: signature_secp256k1.yParity,
418
+ },
419
+ type: 'secp256k1',
420
+ })
421
+ })
422
+
423
+ test('error: throws on invalid size', () => {
424
+ expect(() =>
425
+ SignatureEnvelope.deserialize('0xdeadbeef'),
426
+ ).toThrowErrorMatchingInlineSnapshot(
427
+ `
428
+ [SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: Unknown signature type identifier: 0xde. Expected 0x01 (P256) or 0x02 (WebAuthn)
429
+
430
+ Serialized: 0xdeadbeef]
431
+ `,
432
+ )
433
+ })
434
+
435
+ test('error: throws on invalid yParity', () => {
436
+ // Signature with invalid yParity (must be 0 or 1)
437
+ const invalidSig =
438
+ '0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000102'
439
+ expect(() =>
440
+ SignatureEnvelope.deserialize(invalidSig),
441
+ ).toThrowErrorMatchingInlineSnapshot(
442
+ `[Signature.InvalidYParityError: Value \`2\` is an invalid y-parity value. Y-parity must be 0 or 1.]`,
443
+ )
444
+ })
445
+ })
446
+
447
+ describe('p256', () => {
448
+ test('behavior: deserializes P256 signature', () => {
449
+ const serialized = SignatureEnvelope.serialize(signature_p256)
450
+ const deserialized = SignatureEnvelope.deserialize(serialized)
451
+
452
+ expect(deserialized).toMatchObject({
453
+ signature: {
454
+ r: signature_p256.signature.r,
455
+ s: signature_p256.signature.s,
456
+ },
457
+ publicKey: {
458
+ x: signature_p256.publicKey.x,
459
+ y: signature_p256.publicKey.y,
460
+ },
461
+ prehash: signature_p256.prehash,
462
+ type: 'p256',
463
+ })
464
+ })
465
+
466
+ test('error: throws on invalid P256 signature length', () => {
467
+ // P256 signature with wrong length (should be 130 bytes total, but only 100)
468
+ const invalidSig = `0x01${'00'.repeat(100)}` as `0x${string}`
469
+ expect(() =>
470
+ SignatureEnvelope.deserialize(invalidSig),
471
+ ).toThrowErrorMatchingInlineSnapshot(
472
+ `
473
+ [SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: Invalid P256 signature envelope size: expected 129 bytes, got 100 bytes
474
+
475
+ Serialized: 0x0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]
476
+ `,
477
+ )
478
+ })
479
+ })
480
+
481
+ describe('webAuthn', () => {
482
+ test('behavior: deserializes WebAuthn signature', () => {
483
+ const serialized = SignatureEnvelope.serialize(signature_webauthn)
484
+ const deserialized = SignatureEnvelope.deserialize(serialized)
485
+
486
+ expect(deserialized).toMatchObject({
487
+ signature: {
488
+ r: signature_webauthn.signature.r,
489
+ s: signature_webauthn.signature.s,
490
+ },
491
+ publicKey: {
492
+ x: signature_webauthn.publicKey.x,
493
+ y: signature_webauthn.publicKey.y,
494
+ },
495
+ metadata: {
496
+ authenticatorData: signature_webauthn.metadata.authenticatorData,
497
+ clientDataJSON: signature_webauthn.metadata.clientDataJSON,
498
+ },
499
+ type: 'webAuthn',
500
+ })
501
+ })
502
+
503
+ test('error: throws on invalid WebAuthn signature length', () => {
504
+ // WebAuthn signature too short (must be at least 129 bytes: 1 type + 128 signature data)
505
+ const invalidSig = `0x02${'00'.repeat(100)}` as const
506
+ expect(() =>
507
+ SignatureEnvelope.deserialize(invalidSig),
508
+ ).toThrowErrorMatchingInlineSnapshot(
509
+ `
510
+ [SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: Invalid WebAuthn signature envelope size: expected at least 128 bytes, got 100 bytes
511
+
512
+ Serialized: 0x0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]
513
+ `,
514
+ )
515
+ })
516
+
517
+ test('error: throws on invalid clientDataJSON', () => {
518
+ // Create a signature with invalid JSON (not properly formatted)
519
+ const invalidMetadata = {
520
+ authenticatorData: `0x${'00'.repeat(37)}` as const,
521
+ clientDataJSON: 'not-valid-json',
522
+ }
523
+ const serialized = SignatureEnvelope.serialize({
524
+ ...signature_webauthn,
525
+ metadata: invalidMetadata,
526
+ })
527
+
528
+ expect(() =>
529
+ SignatureEnvelope.deserialize(serialized),
530
+ ).toThrowErrorMatchingInlineSnapshot(
531
+ `
532
+ [SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: Unable to parse WebAuthn metadata: could not extract valid authenticatorData and clientDataJSON
533
+
534
+ Serialized: 0x02000000000000000000000000000000000000000000000000000000000000000000000000006e6f742d76616c69642d6a736f6eccbb3485d4726235f13cb15ef394fb7158179fb7b1925eccec0147671090c52e77c3c53373cc1e3b05e7c23f609deb17cea8fe097300c45411237e9fe4166b35ad8ac16e167d6992c3e120d7f17d2376bc1cbcf30c46ba6dd00ce07303e742f511edf6ce1c32de66846f56afa7be1cbd729bc35750b6d0cdcf3ec9d75461aba0]
535
+ `,
536
+ )
537
+ })
538
+
539
+ test('error: throws on unknown type identifier', () => {
540
+ const unknownType = `0xff${'00'.repeat(129)}` as const
541
+ expect(() =>
542
+ SignatureEnvelope.deserialize(unknownType),
543
+ ).toThrowErrorMatchingInlineSnapshot(
544
+ `
545
+ [SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: Unknown signature type identifier: 0xff. Expected 0x01 (P256) or 0x02 (WebAuthn)
546
+
547
+ Serialized: 0xff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]
548
+ `,
549
+ )
550
+ })
551
+ })
552
+ })
553
+
554
+ describe('from', () => {
555
+ describe('secp256k1', () => {
556
+ test('behavior: coerces from hex string', () => {
557
+ const serialized = Signature.toHex(signature_secp256k1)
558
+
559
+ const envelope = SignatureEnvelope.from(serialized)
560
+
561
+ expect(envelope).toMatchObject({
562
+ signature: {
563
+ r: signature_secp256k1.r,
564
+ s: signature_secp256k1.s,
565
+ yParity: signature_secp256k1.yParity,
566
+ },
567
+ type: 'secp256k1',
568
+ })
569
+ })
570
+
571
+ test('behavior: returns object as-is', () => {
572
+ const envelope: SignatureEnvelope.SignatureEnvelope = {
573
+ signature: signature_secp256k1,
574
+ type: 'secp256k1',
575
+ }
576
+
577
+ const result = SignatureEnvelope.from(envelope)
578
+
579
+ expect(result).toEqual(envelope)
580
+ })
581
+
582
+ test('behavior: coerces from flat signature', () => {
583
+ const result = SignatureEnvelope.from(signature_secp256k1)
584
+
585
+ expect(result).toMatchObject({
586
+ signature: {
587
+ r: signature_secp256k1.r,
588
+ s: signature_secp256k1.s,
589
+ yParity: signature_secp256k1.yParity,
590
+ },
591
+ type: 'secp256k1',
592
+ })
593
+ })
594
+ })
595
+
596
+ describe('p256', () => {
597
+ test('behavior: coerces from hex string', () => {
598
+ const serialized = SignatureEnvelope.serialize(signature_p256)
599
+ const envelope = SignatureEnvelope.from(serialized)
600
+
601
+ expect(envelope).toMatchObject({
602
+ signature: {
603
+ r: signature_p256.signature.r,
604
+ s: signature_p256.signature.s,
605
+ },
606
+ type: 'p256',
607
+ })
608
+ })
609
+
610
+ test('behavior: adds type to object', () => {
611
+ const { type: _, ...withoutType } = signature_p256
612
+ const envelope = SignatureEnvelope.from(withoutType)
613
+
614
+ expect(envelope.type).toBe('p256')
615
+ })
616
+ })
617
+
618
+ describe('webAuthn', () => {
619
+ test('behavior: coerces from hex string', () => {
620
+ const serialized = SignatureEnvelope.serialize(signature_webauthn)
621
+ const envelope = SignatureEnvelope.from(serialized)
622
+
623
+ expect(envelope).toMatchObject({
624
+ signature: {
625
+ r: signature_webauthn.signature.r,
626
+ s: signature_webauthn.signature.s,
627
+ },
628
+ type: 'webAuthn',
629
+ })
630
+ })
631
+
632
+ test('behavior: adds type to object', () => {
633
+ const { type: _, ...withoutType } = signature_webauthn
634
+ const envelope = SignatureEnvelope.from(withoutType)
635
+
636
+ expect(envelope.type).toBe('webAuthn')
637
+ })
638
+ })
639
+ })
640
+
641
+ describe('getType', () => {
642
+ describe('secp256k1', () => {
643
+ test('behavior: returns explicit type', () => {
644
+ const envelope: SignatureEnvelope.SignatureEnvelope = {
645
+ signature: { r: 0n, s: 0n, yParity: 0 },
646
+ type: 'secp256k1',
647
+ }
648
+
649
+ expect(SignatureEnvelope.getType(envelope)).toBe('secp256k1')
650
+ })
651
+
652
+ test('behavior: infers type from properties', () => {
653
+ expect(
654
+ SignatureEnvelope.getType({ signature: signature_secp256k1 }),
655
+ ).toBe('secp256k1')
656
+ })
657
+
658
+ test('behavior: infers type from flat signature', () => {
659
+ const signature = { r: 0n, s: 0n, yParity: 0 }
660
+ expect(SignatureEnvelope.getType(signature)).toBe('secp256k1')
661
+ })
662
+ })
663
+
664
+ describe('p256', () => {
665
+ test('behavior: returns explicit type', () => {
666
+ expect(SignatureEnvelope.getType(signature_p256)).toBe('p256')
667
+ })
668
+
669
+ test('behavior: infers type from properties', () => {
670
+ const { type: _, ...signatureWithoutType } = signature_p256
671
+ expect(SignatureEnvelope.getType(signatureWithoutType)).toBe('p256')
672
+ })
673
+ })
674
+
675
+ describe('webAuthn', () => {
676
+ test('behavior: returns explicit type', () => {
677
+ expect(SignatureEnvelope.getType(signature_webauthn)).toBe('webAuthn')
678
+ })
679
+
680
+ test('behavior: infers type from properties', () => {
681
+ const { type: _, ...signatureWithoutType } = signature_webauthn
682
+ expect(SignatureEnvelope.getType(signatureWithoutType)).toBe('webAuthn')
683
+ })
684
+ })
685
+
686
+ test('error: throws on invalid envelope', () => {
687
+ expect(() =>
688
+ SignatureEnvelope.getType({} as any),
689
+ ).toThrowErrorMatchingInlineSnapshot(
690
+ `[SignatureEnvelope.CoercionError: Unable to coerce value (\`{}\`) to a valid signature envelope.]`,
691
+ )
692
+ })
693
+
694
+ test('error: throws on incomplete signature', () => {
695
+ expect(() =>
696
+ SignatureEnvelope.getType({
697
+ r: 0n,
698
+ s: 0n,
699
+ } as any),
700
+ ).toThrowErrorMatchingInlineSnapshot(
701
+ `[SignatureEnvelope.CoercionError: Unable to coerce value (\`{"r":"0#__bigint","s":"0#__bigint"}\`) to a valid signature envelope.]`,
702
+ )
703
+ })
704
+ })
705
+
706
+ describe('serialize', () => {
707
+ describe('secp256k1', () => {
708
+ test('behavior: serializes with explicit type', () => {
709
+ const envelope: SignatureEnvelope.SignatureEnvelope = {
710
+ signature: signature_secp256k1,
711
+ type: 'secp256k1',
712
+ }
713
+
714
+ const serialized = SignatureEnvelope.serialize(envelope)
715
+
716
+ expect(serialized).toBe(Signature.toHex(signature_secp256k1))
717
+ })
718
+
719
+ test('behavior: serializes without explicit type', () => {
720
+ const serialized = SignatureEnvelope.serialize({
721
+ signature: signature_secp256k1,
722
+ type: 'secp256k1',
723
+ })
724
+
725
+ expect(serialized).toBe(Signature.toHex(signature_secp256k1))
726
+ })
727
+ })
728
+
729
+ describe('p256', () => {
730
+ test('behavior: serializes P256 signature with type identifier', () => {
731
+ const serialized = SignatureEnvelope.serialize(signature_p256)
732
+
733
+ // Should be 130 bytes: 1 (type) + 32 (r) + 32 (s) + 32 (pubKeyX) + 32 (pubKeyY) + 1 (prehash)
734
+ expect(serialized.length).toBe(2 + 130 * 2) // 2 for '0x' prefix + 130 bytes * 2 hex chars
735
+
736
+ // First byte should be P256 type identifier (0x01)
737
+ expect(serialized.slice(0, 4)).toBe('0x01')
738
+ })
739
+
740
+ test('behavior: serializes prehash flag correctly', () => {
741
+ const withPreHashFalse = { ...signature_p256, prehash: false }
742
+ const serialized = SignatureEnvelope.serialize(withPreHashFalse)
743
+
744
+ // Last byte should be 0x00 for false
745
+ expect(serialized.slice(-2)).toBe('00')
746
+ })
747
+ })
748
+
749
+ describe('webAuthn', () => {
750
+ test('behavior: serializes WebAuthn signature with type identifier', () => {
751
+ const serialized = SignatureEnvelope.serialize(signature_webauthn)
752
+
753
+ // Should be: 1 (type) + authenticatorData.length + clientDataJSON.length + 128 (signature components)
754
+ const authDataLength =
755
+ (signature_webauthn.metadata.authenticatorData.length - 2) / 2
756
+ const clientDataLength = signature_webauthn.metadata.clientDataJSON.length
757
+ const expectedLength =
758
+ 2 + (1 + authDataLength + clientDataLength + 128) * 2
759
+
760
+ expect(serialized.length).toBe(expectedLength)
761
+
762
+ // First byte should be WebAuthn type identifier (0x02)
763
+ expect(serialized.slice(0, 4)).toBe('0x02')
764
+ })
765
+
766
+ test('behavior: preserves metadata', () => {
767
+ const serialized = SignatureEnvelope.serialize(signature_webauthn)
768
+ const deserialized = SignatureEnvelope.deserialize(serialized)
769
+
770
+ expect(deserialized.metadata?.authenticatorData).toBe(
771
+ signature_webauthn.metadata.authenticatorData,
772
+ )
773
+ expect(deserialized.metadata?.clientDataJSON).toBe(
774
+ signature_webauthn.metadata.clientDataJSON,
775
+ )
776
+ })
777
+ })
778
+
779
+ describe('roundtrip', () => {
780
+ describe('secp256k1', () => {
781
+ test('behavior: roundtrips serialize -> deserialize', () => {
782
+ const envelope: SignatureEnvelope.Secp256k1 = {
783
+ signature: signature_secp256k1,
784
+ type: 'secp256k1',
785
+ }
786
+
787
+ const serialized = SignatureEnvelope.serialize(envelope)
788
+ const deserialized = SignatureEnvelope.deserialize(serialized)
789
+
790
+ expect(deserialized).toMatchObject({
791
+ signature: {
792
+ r: signature_secp256k1.r,
793
+ s: signature_secp256k1.s,
794
+ yParity: signature_secp256k1.yParity,
795
+ },
796
+ type: 'secp256k1',
797
+ })
798
+ })
799
+ })
800
+
801
+ describe('p256', () => {
802
+ test('behavior: roundtrips serialize -> deserialize', () => {
803
+ const serialized = SignatureEnvelope.serialize(signature_p256)
804
+ const deserialized = SignatureEnvelope.deserialize(serialized)
805
+
806
+ expect(deserialized).toMatchObject({
807
+ signature: {
808
+ r: signature_p256.signature.r,
809
+ s: signature_p256.signature.s,
810
+ },
811
+ publicKey: {
812
+ x: signature_p256.publicKey.x,
813
+ y: signature_p256.publicKey.y,
814
+ },
815
+ prehash: signature_p256.prehash,
816
+ type: 'p256',
817
+ })
818
+ })
819
+
820
+ test('behavior: handles prehash=false', () => {
821
+ const signature = { ...signature_p256, prehash: false }
822
+ const serialized = SignatureEnvelope.serialize(signature)
823
+ const deserialized = SignatureEnvelope.deserialize(serialized)
824
+
825
+ expect(deserialized.prehash).toBe(false)
826
+ })
827
+ })
828
+
829
+ describe('webAuthn', () => {
830
+ test('behavior: roundtrips serialize -> deserialize', () => {
831
+ const serialized = SignatureEnvelope.serialize(signature_webauthn)
832
+ const deserialized = SignatureEnvelope.deserialize(serialized)
833
+
834
+ expect(deserialized).toMatchObject({
835
+ signature: {
836
+ r: signature_webauthn.signature.r,
837
+ s: signature_webauthn.signature.s,
838
+ },
839
+ publicKey: {
840
+ x: signature_webauthn.publicKey.x,
841
+ y: signature_webauthn.publicKey.y,
842
+ },
843
+ metadata: {
844
+ authenticatorData: signature_webauthn.metadata.authenticatorData,
845
+ clientDataJSON: signature_webauthn.metadata.clientDataJSON,
846
+ },
847
+ type: 'webAuthn',
848
+ })
849
+ })
850
+
851
+ test('behavior: handles variable-length clientDataJSON', () => {
852
+ const longClientData = JSON.stringify({
853
+ type: 'webAuthn.get',
854
+ challenge: 'a'.repeat(100),
855
+ origin: 'https://example.com',
856
+ })
857
+
858
+ const signatureWithLongData = {
859
+ ...signature_webauthn,
860
+ metadata: {
861
+ ...signature_webauthn.metadata,
862
+ clientDataJSON: longClientData,
863
+ },
864
+ }
865
+
866
+ const serialized = SignatureEnvelope.serialize(signatureWithLongData)
867
+ const deserialized = SignatureEnvelope.deserialize(serialized)
868
+
869
+ expect(deserialized.metadata?.clientDataJSON).toBe(longClientData)
870
+ })
871
+ })
872
+ })
873
+
874
+ test('error: throws on invalid envelope', () => {
875
+ const error = (() => {
876
+ try {
877
+ SignatureEnvelope.serialize({} as any)
878
+ } catch (e) {
879
+ return e
880
+ }
881
+ })() as SignatureEnvelope.CoercionError
882
+ expect(error).toBeInstanceOf(SignatureEnvelope.CoercionError)
883
+ expect(error.message).toMatchInlineSnapshot(
884
+ `"Unable to coerce value (\`{}\`) to a valid signature envelope."`,
885
+ )
886
+ })
887
+ })
888
+
889
+ describe('validate', () => {
890
+ describe('secp256k1', () => {
891
+ test('behavior: returns true for valid signature', () => {
892
+ expect(
893
+ SignatureEnvelope.validate({
894
+ signature: signature_secp256k1,
895
+ type: 'secp256k1',
896
+ }),
897
+ ).toBe(true)
898
+ })
899
+
900
+ test('behavior: returns true for signature without explicit type', () => {
901
+ expect(
902
+ SignatureEnvelope.validate({ signature: signature_secp256k1 }),
903
+ ).toBe(true)
904
+ })
905
+
906
+ test('behavior: returns false for invalid signature values', () => {
907
+ expect(
908
+ SignatureEnvelope.validate({
909
+ signature: {
910
+ r: 0n,
911
+ s: 0n,
912
+ yParity: 2,
913
+ },
914
+ type: 'secp256k1',
915
+ }),
916
+ ).toBe(false)
917
+ })
918
+ })
919
+
920
+ describe('p256', () => {
921
+ test('behavior: returns true for valid P256 signature', () => {
922
+ expect(SignatureEnvelope.validate(signature_p256)).toBe(true)
923
+ })
924
+
925
+ test('behavior: returns false for invalid P256 signature', () => {
926
+ expect(
927
+ SignatureEnvelope.validate({
928
+ ...signature_p256,
929
+ prehash: 'invalid' as any,
930
+ }),
931
+ ).toBe(false)
932
+ })
933
+ })
934
+
935
+ describe('webAuthn', () => {
936
+ test('behavior: returns true for valid WebAuthn signature', () => {
937
+ expect(SignatureEnvelope.validate(signature_webauthn)).toBe(true)
938
+ })
939
+
940
+ test('behavior: returns false for invalid WebAuthn signature', () => {
941
+ const { metadata: _, ...withoutMetadata } = signature_webauthn
942
+ expect(SignatureEnvelope.validate(withoutMetadata as any)).toBe(false)
943
+ })
944
+ })
945
+
946
+ test('behavior: returns false for invalid envelope', () => {
947
+ expect(SignatureEnvelope.validate({} as any)).toBe(false)
948
+ })
949
+
950
+ test('behavior: returns false for incomplete signature', () => {
951
+ expect(
952
+ SignatureEnvelope.validate({
953
+ r: 0n,
954
+ s: 0n,
955
+ } as any),
956
+ ).toBe(false)
957
+ })
958
+ })
959
+
960
+ describe('fromRpc', () => {
961
+ describe('secp256k1', () => {
962
+ test('behavior: converts RPC secp256k1 signature', () => {
963
+ const rpc: SignatureEnvelope.Secp256k1Rpc = {
964
+ r: Signature.toRpc(signature_secp256k1).r,
965
+ s: Signature.toRpc(signature_secp256k1).s,
966
+ yParity: Signature.toRpc(signature_secp256k1).yParity,
967
+ type: 'secp256k1',
968
+ }
969
+
970
+ const envelope = SignatureEnvelope.fromRpc(rpc)
971
+
972
+ expect(envelope).toMatchObject({
973
+ signature: {
974
+ r: signature_secp256k1.r,
975
+ s: signature_secp256k1.s,
976
+ yParity: signature_secp256k1.yParity,
977
+ },
978
+ type: 'secp256k1',
979
+ })
980
+ })
981
+ })
982
+
983
+ describe('p256', () => {
984
+ test('behavior: converts RPC P256 signature', () => {
985
+ const rpc: SignatureEnvelope.P256Rpc = {
986
+ prehash: true,
987
+ pubKeyX: Hex.fromNumber(publicKey.x, { size: 32 }),
988
+ pubKeyY: Hex.fromNumber(publicKey.y, { size: 32 }),
989
+ r: Hex.fromNumber(p256Signature.r, { size: 32 }),
990
+ s: Hex.fromNumber(p256Signature.s, { size: 32 }),
991
+ type: 'p256',
992
+ }
993
+
994
+ const envelope = SignatureEnvelope.fromRpc(rpc)
995
+
996
+ expect(envelope).toMatchObject({
997
+ prehash: true,
998
+ publicKey: {
999
+ x: publicKey.x,
1000
+ y: publicKey.y,
1001
+ },
1002
+ signature: {
1003
+ r: p256Signature.r,
1004
+ s: p256Signature.s,
1005
+ },
1006
+ type: 'p256',
1007
+ })
1008
+ })
1009
+ })
1010
+
1011
+ describe('webAuthn', () => {
1012
+ test('behavior: converts RPC WebAuthn signature', () => {
1013
+ const webauthnData = WebAuthnP256.getAuthenticatorData({
1014
+ rpId: 'localhost',
1015
+ })
1016
+ const clientDataJSON = WebAuthnP256.getClientDataJSON({
1017
+ challenge: '0xdeadbeef',
1018
+ origin: 'http://localhost',
1019
+ })
1020
+
1021
+ const rpc: SignatureEnvelope.WebAuthnRpc = {
1022
+ pubKeyX: Hex.fromNumber(publicKey.x, { size: 32 }),
1023
+ pubKeyY: Hex.fromNumber(publicKey.y, { size: 32 }),
1024
+ r: Hex.fromNumber(p256Signature.r, { size: 32 }),
1025
+ s: Hex.fromNumber(p256Signature.s, { size: 32 }),
1026
+ type: 'webAuthn',
1027
+ webauthnData: Hex.concat(webauthnData, Hex.fromString(clientDataJSON)),
1028
+ }
1029
+
1030
+ const envelope = SignatureEnvelope.fromRpc(rpc)
1031
+
1032
+ expect(envelope).toMatchObject({
1033
+ metadata: {
1034
+ authenticatorData: webauthnData,
1035
+ clientDataJSON,
1036
+ },
1037
+ publicKey: {
1038
+ x: publicKey.x,
1039
+ y: publicKey.y,
1040
+ },
1041
+ signature: {
1042
+ r: p256Signature.r,
1043
+ s: p256Signature.s,
1044
+ },
1045
+ type: 'webAuthn',
1046
+ })
1047
+ })
1048
+ })
1049
+ })
1050
+
1051
+ describe('toRpc', () => {
1052
+ describe('secp256k1', () => {
1053
+ test('behavior: converts secp256k1 signature to RPC', () => {
1054
+ const envelope: SignatureEnvelope.Secp256k1 = {
1055
+ signature: signature_secp256k1,
1056
+ type: 'secp256k1',
1057
+ }
1058
+
1059
+ const rpc = SignatureEnvelope.toRpc(envelope)
1060
+
1061
+ expect(rpc).toMatchObject({
1062
+ r: Signature.toRpc(signature_secp256k1).r,
1063
+ s: Signature.toRpc(signature_secp256k1).s,
1064
+ yParity: Signature.toRpc(signature_secp256k1).yParity,
1065
+ type: 'secp256k1',
1066
+ })
1067
+ })
1068
+ })
1069
+
1070
+ describe('p256', () => {
1071
+ test('behavior: converts P256 signature to RPC', () => {
1072
+ const rpc = SignatureEnvelope.toRpc(signature_p256)
1073
+
1074
+ expect(rpc.type).toBe('p256')
1075
+ expect(rpc.prehash).toBe(true)
1076
+ expect(typeof rpc.pubKeyX).toBe('string')
1077
+ expect(typeof rpc.pubKeyY).toBe('string')
1078
+ expect(typeof rpc.r).toBe('string')
1079
+ expect(typeof rpc.s).toBe('string')
1080
+ })
1081
+
1082
+ test('behavior: converts prehash=false correctly', () => {
1083
+ const withPrehashFalse = { ...signature_p256, prehash: false }
1084
+ const rpc = SignatureEnvelope.toRpc(withPrehashFalse)
1085
+
1086
+ expect(rpc.prehash).toBe(false)
1087
+ })
1088
+ })
1089
+
1090
+ describe('webAuthn', () => {
1091
+ test('behavior: converts WebAuthn signature to RPC', () => {
1092
+ const rpc = SignatureEnvelope.toRpc(
1093
+ signature_webauthn,
1094
+ ) as SignatureEnvelope.WebAuthnRpc
1095
+
1096
+ expect(rpc.type).toBe('webAuthn')
1097
+ expect(typeof rpc.pubKeyX).toBe('string')
1098
+ expect(typeof rpc.pubKeyY).toBe('string')
1099
+ expect(typeof rpc.r).toBe('string')
1100
+ expect(typeof rpc.s).toBe('string')
1101
+ expect(typeof rpc.webauthnData).toBe('string')
1102
+ expect(rpc.webauthnData.startsWith('0x')).toBe(true)
1103
+ })
1104
+
1105
+ test('behavior: webauthnData contains authenticatorData and clientDataJSON', () => {
1106
+ const rpc = SignatureEnvelope.toRpc(
1107
+ signature_webauthn,
1108
+ ) as SignatureEnvelope.WebAuthnRpc
1109
+
1110
+ // webauthnData should contain the concatenation of authenticatorData and clientDataJSON (as hex)
1111
+ expect(rpc.webauthnData).toContain(
1112
+ signature_webauthn.metadata.authenticatorData.slice(2),
1113
+ )
1114
+ })
1115
+ })
1116
+ })
1117
+
1118
+ describe('roundtrip: toRpc <-> fromRpc', () => {
1119
+ describe('secp256k1', () => {
1120
+ test('behavior: roundtrips toRpc -> fromRpc', () => {
1121
+ const envelope: SignatureEnvelope.Secp256k1 = {
1122
+ signature: signature_secp256k1,
1123
+ type: 'secp256k1',
1124
+ }
1125
+
1126
+ const rpc = SignatureEnvelope.toRpc(envelope)
1127
+ const roundtripped = SignatureEnvelope.fromRpc(rpc)
1128
+
1129
+ expect(roundtripped).toMatchObject({
1130
+ signature: {
1131
+ r: signature_secp256k1.r,
1132
+ s: signature_secp256k1.s,
1133
+ yParity: signature_secp256k1.yParity,
1134
+ },
1135
+ type: 'secp256k1',
1136
+ })
1137
+ })
1138
+
1139
+ test('behavior: roundtrips fromRpc -> toRpc', () => {
1140
+ const rpc: SignatureEnvelope.Secp256k1Rpc = {
1141
+ r: Signature.toRpc(signature_secp256k1).r,
1142
+ s: Signature.toRpc(signature_secp256k1).s,
1143
+ yParity: Signature.toRpc(signature_secp256k1).yParity,
1144
+ type: 'secp256k1',
1145
+ }
1146
+
1147
+ const envelope = SignatureEnvelope.fromRpc(rpc)
1148
+ const roundtripped = SignatureEnvelope.toRpc(envelope)
1149
+
1150
+ expect(roundtripped).toMatchObject(rpc)
1151
+ })
1152
+ })
1153
+
1154
+ describe('p256', () => {
1155
+ test('behavior: roundtrips toRpc -> fromRpc', () => {
1156
+ const rpc = SignatureEnvelope.toRpc(signature_p256)
1157
+ const roundtripped = SignatureEnvelope.fromRpc(rpc)
1158
+
1159
+ expect(roundtripped).toMatchObject({
1160
+ prehash: signature_p256.prehash,
1161
+ publicKey: {
1162
+ x: signature_p256.publicKey.x,
1163
+ y: signature_p256.publicKey.y,
1164
+ },
1165
+ signature: {
1166
+ r: signature_p256.signature.r,
1167
+ s: signature_p256.signature.s,
1168
+ },
1169
+ type: 'p256',
1170
+ })
1171
+ })
1172
+
1173
+ test('behavior: handles prehash=false in roundtrip', () => {
1174
+ const withPrehashFalse = { ...signature_p256, prehash: false }
1175
+ const rpc = SignatureEnvelope.toRpc(withPrehashFalse)
1176
+ const roundtripped = SignatureEnvelope.fromRpc(rpc)
1177
+
1178
+ expect(roundtripped.prehash).toBe(false)
1179
+ })
1180
+ })
1181
+
1182
+ describe('webAuthn', () => {
1183
+ test('behavior: roundtrips toRpc -> fromRpc', () => {
1184
+ const rpc = SignatureEnvelope.toRpc(signature_webauthn)
1185
+ const roundtripped = SignatureEnvelope.fromRpc(rpc)
1186
+
1187
+ expect(roundtripped).toMatchObject({
1188
+ metadata: {
1189
+ authenticatorData: signature_webauthn.metadata.authenticatorData,
1190
+ clientDataJSON: signature_webauthn.metadata.clientDataJSON,
1191
+ },
1192
+ publicKey: {
1193
+ x: signature_webauthn.publicKey.x,
1194
+ y: signature_webauthn.publicKey.y,
1195
+ },
1196
+ signature: {
1197
+ r: signature_webauthn.signature.r,
1198
+ s: signature_webauthn.signature.s,
1199
+ },
1200
+ type: 'webAuthn',
1201
+ })
1202
+ })
1203
+
1204
+ test('behavior: handles variable-length clientDataJSON in roundtrip', () => {
1205
+ const longClientData = JSON.stringify({
1206
+ type: 'webAuthn.get',
1207
+ challenge: 'a'.repeat(100),
1208
+ origin: 'https://example.com',
1209
+ crossOrigin: false,
1210
+ })
1211
+
1212
+ const signatureWithLongData = {
1213
+ ...signature_webauthn,
1214
+ metadata: {
1215
+ ...signature_webauthn.metadata,
1216
+ clientDataJSON: longClientData,
1217
+ },
1218
+ }
1219
+
1220
+ const rpc = SignatureEnvelope.toRpc(signatureWithLongData)
1221
+ const roundtripped = SignatureEnvelope.fromRpc(rpc)
1222
+
1223
+ expect(roundtripped.metadata?.clientDataJSON).toBe(longClientData)
1224
+ })
1225
+ })
1226
+ })
1227
+
1228
+ describe('types', () => {
1229
+ test('behavior: contains all signature types', () => {
1230
+ expect(SignatureEnvelope.types).toEqual(['secp256k1', 'p256', 'webAuthn'])
1231
+ })
1232
+ })
1233
+
1234
+ describe('CoercionError', () => {
1235
+ test('behavior: formats error message with hex string', () => {
1236
+ const error = new SignatureEnvelope.CoercionError({
1237
+ envelope: '0xdeadbeef',
1238
+ })
1239
+ expect(error).toMatchInlineSnapshot(
1240
+ `[SignatureEnvelope.CoercionError: Unable to coerce value (\`"0xdeadbeef"\`) to a valid signature envelope.]`,
1241
+ )
1242
+ })
1243
+
1244
+ test('behavior: formats error message with object', () => {
1245
+ const error = new SignatureEnvelope.CoercionError({
1246
+ envelope: { r: 0n, s: 0n, yParity: 0 },
1247
+ })
1248
+ expect(error).toMatchInlineSnapshot(
1249
+ `[SignatureEnvelope.CoercionError: Unable to coerce value (\`{"r":"0#__bigint","s":"0#__bigint","yParity":0}\`) to a valid signature envelope.]`,
1250
+ )
1251
+ })
1252
+ })