ox 1.7.2 → 1.7.5
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 +43 -0
- package/dist/core/AbiEvent.d.ts.map +1 -1
- package/dist/core/AbiEvent.js +10 -9
- package/dist/core/AbiEvent.js.map +1 -1
- package/dist/core/Blobs.d.ts.map +1 -1
- package/dist/core/Blobs.js +6 -2
- package/dist/core/Blobs.js.map +1 -1
- package/dist/core/Signature.d.ts +6 -5
- package/dist/core/Signature.d.ts.map +1 -1
- package/dist/core/Signature.js +17 -7
- package/dist/core/Signature.js.map +1 -1
- package/dist/core/Siwe.d.ts.map +1 -1
- package/dist/core/Siwe.js +21 -1
- package/dist/core/Siwe.js.map +1 -1
- package/dist/tempo/KeyAuthorization.d.ts +43 -39
- package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/KeyAuthorization.js +24 -4
- package/dist/tempo/KeyAuthorization.js.map +1 -1
- package/dist/tempo/MultisigConfig.d.ts +18 -15
- package/dist/tempo/MultisigConfig.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.js +49 -20
- package/dist/tempo/MultisigConfig.js.map +1 -1
- package/dist/tempo/MultisigOperation.d.ts +45 -28
- package/dist/tempo/MultisigOperation.d.ts.map +1 -1
- package/dist/tempo/MultisigOperation.js +81 -131
- package/dist/tempo/MultisigOperation.js.map +1 -1
- package/dist/tempo/MultisigSimulation.d.ts +85 -0
- package/dist/tempo/MultisigSimulation.d.ts.map +1 -0
- package/dist/tempo/MultisigSimulation.js +112 -0
- package/dist/tempo/MultisigSimulation.js.map +1 -0
- package/dist/tempo/RpcSchemaTempo.d.ts +20 -10
- package/dist/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +74 -153
- package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.js +130 -187
- package/dist/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/tempo/TransactionRequest.d.ts +9 -7
- package/dist/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/tempo/TransactionRequest.js +12 -7
- package/dist/tempo/TransactionRequest.js.map +1 -1
- package/dist/tempo/index.d.ts +7 -5
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +7 -5
- package/dist/tempo/index.js.map +1 -1
- package/dist/zod/tempo/AuthorizationTempo.d.ts +360 -140
- package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.d.ts +247 -487
- package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.js +29 -31
- package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
- package/dist/zod/tempo/MultisigOperation.d.ts +0 -10
- package/dist/zod/tempo/MultisigOperation.d.ts.map +1 -1
- package/dist/zod/tempo/MultisigOperation.js +0 -3
- package/dist/zod/tempo/MultisigOperation.js.map +1 -1
- package/dist/zod/tempo/MultisigSimulation.d.ts +52 -0
- package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -0
- package/dist/zod/tempo/MultisigSimulation.js +44 -0
- package/dist/zod/tempo/MultisigSimulation.js.map +1 -0
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +357 -422
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.js +19 -10
- package/dist/zod/tempo/RpcSchemaTempo.js.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.d.ts +108 -42
- package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.js +21 -19
- package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +678 -666
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.d.ts +580 -619
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.js +18 -10
- package/dist/zod/tempo/TransactionRequest.js.map +1 -1
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts +327 -213
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +105 -3
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
- package/dist/zod/tempo/z.d.ts +1 -1
- package/dist/zod/tempo/z.d.ts.map +1 -1
- package/dist/zod/tempo/z.js +1 -1
- package/dist/zod/tempo/z.js.map +1 -1
- package/package.json +9 -9
- package/src/core/AbiEvent.ts +11 -9
- package/src/core/Blobs.ts +6 -2
- package/src/core/Signature.ts +41 -11
- package/src/core/Siwe.ts +20 -1
- package/src/core/_test/AbiEvent.test.ts +45 -0
- package/src/core/_test/Blobs.test.ts +22 -0
- package/src/core/_test/Signature.test.ts +166 -0
- package/src/core/_test/Siwe.test.ts +100 -1
- package/src/tempo/AuthorizationTempo.test.ts +8 -0
- package/src/tempo/KeyAuthorization.test-d.ts +16 -74
- package/src/tempo/KeyAuthorization.test.ts +113 -14
- package/src/tempo/KeyAuthorization.ts +110 -66
- package/src/tempo/MultisigConfig.test.ts +44 -20
- package/src/tempo/MultisigConfig.ts +65 -23
- package/src/tempo/MultisigOperation.test-d.ts +8 -6
- package/src/tempo/MultisigOperation.test.ts +187 -591
- package/src/tempo/MultisigOperation.ts +129 -205
- package/src/tempo/MultisigSimulation.test-d.ts +33 -0
- package/src/tempo/MultisigSimulation.test.ts +194 -0
- package/src/tempo/MultisigSimulation.ts +192 -0
- package/src/tempo/RpcSchemaTempo.test-d.ts +45 -10
- package/src/tempo/RpcSchemaTempo.ts +15 -6
- package/src/tempo/SignatureEnvelope.test-d.ts +27 -101
- package/src/tempo/SignatureEnvelope.test.ts +330 -533
- package/src/tempo/SignatureEnvelope.ts +198 -310
- package/src/tempo/Transaction.test.ts +61 -0
- package/src/tempo/TransactionRequest.test-d.ts +7 -7
- package/src/tempo/TransactionRequest.test.ts +81 -223
- package/src/tempo/TransactionRequest.ts +34 -13
- package/src/tempo/index.ts +7 -5
- package/src/tempo/multisig.e2e.test.ts +329 -818
- package/src/version.ts +1 -1
- package/src/zod/tempo/KeyAuthorization.ts +50 -39
- package/src/zod/tempo/MultisigOperation.ts +0 -3
- package/src/zod/tempo/MultisigSimulation.ts +53 -0
- package/src/zod/tempo/RpcSchemaTempo.ts +20 -10
- package/src/zod/tempo/SignatureEnvelope.ts +46 -59
- package/src/zod/tempo/TransactionRequest.ts +28 -14
- package/src/zod/tempo/_test/KeyAuthorization.test.ts +66 -30
- package/src/zod/tempo/_test/MultisigOperation.test.ts +0 -2
- package/src/zod/tempo/_test/MultisigSimulation.test-d.ts +13 -0
- package/src/zod/tempo/_test/MultisigSimulation.test.ts +53 -0
- package/src/zod/tempo/_test/RpcSchemaTempo.test-d.ts +19 -2
- package/src/zod/tempo/_test/RpcSchemaTempo.test.ts +46 -4
- package/src/zod/tempo/_test/SignatureEnvelope.test.ts +108 -129
- package/src/zod/tempo/_test/TransactionRequest.test.ts +17 -168
- package/src/zod/tempo/z.ts +1 -1
- package/dist/tempo/MultisigWitness.d.ts +0 -106
- package/dist/tempo/MultisigWitness.d.ts.map +0 -1
- package/dist/tempo/MultisigWitness.js +0 -118
- package/dist/tempo/MultisigWitness.js.map +0 -1
- package/dist/zod/tempo/MultisigWitness.d.ts +0 -152
- package/dist/zod/tempo/MultisigWitness.d.ts.map +0 -1
- package/dist/zod/tempo/MultisigWitness.js +0 -76
- package/dist/zod/tempo/MultisigWitness.js.map +0 -1
- package/src/tempo/MultisigWitness.test-d.ts +0 -42
- package/src/tempo/MultisigWitness.test.ts +0 -288
- package/src/tempo/MultisigWitness.ts +0 -210
- package/src/zod/tempo/MultisigWitness.ts +0 -101
- package/src/zod/tempo/_test/MultisigWitness.test-d.ts +0 -13
- package/src/zod/tempo/_test/MultisigWitness.test.ts +0 -149
|
@@ -572,6 +572,172 @@ describe('toBytes', () => {
|
|
|
572
572
|
})
|
|
573
573
|
})
|
|
574
574
|
|
|
575
|
+
describe('toCompactBytes', () => {
|
|
576
|
+
test('default', () => {
|
|
577
|
+
const bytes = Signature.toCompactBytes({
|
|
578
|
+
r: '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf',
|
|
579
|
+
s: '0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
|
|
580
|
+
yParity: 1,
|
|
581
|
+
})
|
|
582
|
+
expect(bytes.length).toBe(64)
|
|
583
|
+
expect(Hex.fromBytes(bytes)).toBe(
|
|
584
|
+
'0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
|
|
585
|
+
)
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
test('behavior: r/s shorter than 32 bytes are left-padded, not right-padded', () => {
|
|
589
|
+
const bytes = Signature.toCompactBytes({
|
|
590
|
+
r: '0x01',
|
|
591
|
+
s: '0x02',
|
|
592
|
+
yParity: 0,
|
|
593
|
+
})
|
|
594
|
+
// `r` occupies the first 32 bytes and must be the big-endian integer `1`
|
|
595
|
+
// (left-padded), not `1` shifted into the top byte (right-padded).
|
|
596
|
+
expect(Hex.fromBytes(bytes.subarray(0, 32))).toBe(
|
|
597
|
+
'0x0000000000000000000000000000000000000000000000000000000000000001',
|
|
598
|
+
)
|
|
599
|
+
expect(Hex.fromBytes(bytes.subarray(32, 64))).toBe(
|
|
600
|
+
'0x0000000000000000000000000000000000000000000000000000000000000002',
|
|
601
|
+
)
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
test('behavior: round-trips through recoverPublicKey for a signature whose r has a leading zero byte', async () => {
|
|
605
|
+
const { Secp256k1 } = await import('ox')
|
|
606
|
+
const payload = `0x${'11'.repeat(32)}` as const
|
|
607
|
+
const privateKey = Hex.fromNumber(4n * 123456789013n + 7n, { size: 32 })
|
|
608
|
+
const signature = Secp256k1.sign({ payload, privateKey })
|
|
609
|
+
// sanity: this fixture's `r` genuinely has a leading zero byte
|
|
610
|
+
expect(signature.r.startsWith('0x00')).toBe(true)
|
|
611
|
+
const expectedPublicKey = Secp256k1.recoverPublicKey({ payload, signature })
|
|
612
|
+
|
|
613
|
+
// simulate an external source (RPC/subgraph/DB) that stores `r` in
|
|
614
|
+
// minimal (non-zero-padded) form, as the `Hex.Hex` type permits
|
|
615
|
+
const minimalR = `0x${signature.r.slice(4)}` as const
|
|
616
|
+
const recoveredBytes = Signature.toRecoveredBytes({
|
|
617
|
+
r: minimalR,
|
|
618
|
+
s: signature.s,
|
|
619
|
+
yParity: signature.yParity,
|
|
620
|
+
})
|
|
621
|
+
const roundTripped = Signature.fromRecoveredBytes(recoveredBytes)
|
|
622
|
+
const recoveredPublicKey = Secp256k1.recoverPublicKey({
|
|
623
|
+
payload,
|
|
624
|
+
signature: roundTripped,
|
|
625
|
+
})
|
|
626
|
+
expect(recoveredPublicKey.x).toBe(expectedPublicKey.x)
|
|
627
|
+
expect(recoveredPublicKey.y).toBe(expectedPublicKey.y)
|
|
628
|
+
})
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
describe('fromCompactBytes', () => {
|
|
632
|
+
test('default', () => {
|
|
633
|
+
const signature = Signature.fromCompactBytes(
|
|
634
|
+
Bytes.fromHex(
|
|
635
|
+
'0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
|
|
636
|
+
),
|
|
637
|
+
)
|
|
638
|
+
expect(signature).toStrictEqual({
|
|
639
|
+
r: '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf',
|
|
640
|
+
s: '0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
|
|
641
|
+
})
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
test('behavior: throws on a byte length other than 64', () => {
|
|
645
|
+
expect(() => Signature.fromCompactBytes(new Uint8Array(65)))
|
|
646
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
647
|
+
[Signature.InvalidSerializedSizeError: Value \`0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\` is an invalid signature size.
|
|
648
|
+
|
|
649
|
+
Expected: 64 bytes.
|
|
650
|
+
Received 65 bytes.]
|
|
651
|
+
`)
|
|
652
|
+
})
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
describe('toRecoveredBytes', () => {
|
|
656
|
+
test('default', () => {
|
|
657
|
+
const bytes = Signature.toRecoveredBytes({
|
|
658
|
+
r: '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf',
|
|
659
|
+
s: '0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
|
|
660
|
+
yParity: 1,
|
|
661
|
+
})
|
|
662
|
+
expect(bytes.length).toBe(65)
|
|
663
|
+
expect(bytes[0]).toBe(1)
|
|
664
|
+
expect(Hex.fromBytes(bytes.subarray(1))).toBe(
|
|
665
|
+
'0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
|
|
666
|
+
)
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
test('behavior: r/s shorter than 32 bytes are left-padded, not right-padded', () => {
|
|
670
|
+
const bytes = Signature.toRecoveredBytes({
|
|
671
|
+
r: '0x01',
|
|
672
|
+
s: '0x02',
|
|
673
|
+
yParity: 0,
|
|
674
|
+
})
|
|
675
|
+
expect(Hex.fromBytes(bytes.subarray(1, 33))).toBe(
|
|
676
|
+
'0x0000000000000000000000000000000000000000000000000000000000000001',
|
|
677
|
+
)
|
|
678
|
+
expect(Hex.fromBytes(bytes.subarray(33, 65))).toBe(
|
|
679
|
+
'0x0000000000000000000000000000000000000000000000000000000000000002',
|
|
680
|
+
)
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
test('behavior: throws before coercing an out-of-range yParity', () => {
|
|
684
|
+
expect(() =>
|
|
685
|
+
Signature.toRecoveredBytes({
|
|
686
|
+
r: '0x01',
|
|
687
|
+
s: '0x02',
|
|
688
|
+
yParity: 256,
|
|
689
|
+
}),
|
|
690
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
691
|
+
`[Signature.InvalidYParityError: Value \`256\` is an invalid y-parity value. Y-parity must be 0 or 1.]`,
|
|
692
|
+
)
|
|
693
|
+
})
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
describe('fromRecoveredBytes', () => {
|
|
697
|
+
test('default', () => {
|
|
698
|
+
const bytes = new Uint8Array(65)
|
|
699
|
+
bytes[0] = 1
|
|
700
|
+
bytes.set(
|
|
701
|
+
Bytes.fromHex(
|
|
702
|
+
'0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf',
|
|
703
|
+
),
|
|
704
|
+
1,
|
|
705
|
+
)
|
|
706
|
+
bytes.set(
|
|
707
|
+
Bytes.fromHex(
|
|
708
|
+
'0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
|
|
709
|
+
),
|
|
710
|
+
33,
|
|
711
|
+
)
|
|
712
|
+
const signature = Signature.fromRecoveredBytes(bytes)
|
|
713
|
+
expect(signature).toStrictEqual({
|
|
714
|
+
r: '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf',
|
|
715
|
+
s: '0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
|
|
716
|
+
yParity: 1,
|
|
717
|
+
})
|
|
718
|
+
})
|
|
719
|
+
|
|
720
|
+
test('behavior: throws on a byte length other than 65', () => {
|
|
721
|
+
expect(() => Signature.fromRecoveredBytes(new Uint8Array(64)))
|
|
722
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
723
|
+
[Signature.InvalidSerializedSizeError: Value \`0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\` is an invalid signature size.
|
|
724
|
+
|
|
725
|
+
Expected: 65 bytes.
|
|
726
|
+
Received 64 bytes.]
|
|
727
|
+
`)
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
test('behavior: throws on an out-of-range yParity byte', () => {
|
|
731
|
+
const bytes = new Uint8Array(65)
|
|
732
|
+
bytes[0] = 7
|
|
733
|
+
expect(() =>
|
|
734
|
+
Signature.fromRecoveredBytes(bytes),
|
|
735
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
736
|
+
`[Signature.InvalidYParityError: Value \`7\` is an invalid y-parity value. Y-parity must be 0 or 1.]`,
|
|
737
|
+
)
|
|
738
|
+
})
|
|
739
|
+
})
|
|
740
|
+
|
|
575
741
|
describe('toDerHex', () => {
|
|
576
742
|
test('default', () => {
|
|
577
743
|
const signature = Signature.from({
|
|
@@ -324,6 +324,53 @@ describe('createMessage', () => {
|
|
|
324
324
|
`)
|
|
325
325
|
})
|
|
326
326
|
|
|
327
|
+
test('behavior: uri with no path (authority only)', () => {
|
|
328
|
+
vi.useFakeTimers()
|
|
329
|
+
vi.setSystemTime(new Date(Date.UTC(2023, 1, 1)))
|
|
330
|
+
|
|
331
|
+
expect(Siwe.createMessage({ ...message, uri: 'https://example.com' }))
|
|
332
|
+
.toMatchInlineSnapshot(`
|
|
333
|
+
"example.com wants you to sign in with your Ethereum account:
|
|
334
|
+
0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
URI: https://example.com
|
|
338
|
+
Version: 1
|
|
339
|
+
Chain ID: 1
|
|
340
|
+
Nonce: foobarbaz
|
|
341
|
+
Issued At: 2023-02-01T00:00:00.000Z"
|
|
342
|
+
`)
|
|
343
|
+
|
|
344
|
+
vi.useRealTimers()
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
test('behavior: resources entry with no path (authority only)', () => {
|
|
348
|
+
vi.useFakeTimers()
|
|
349
|
+
vi.setSystemTime(new Date(Date.UTC(2023, 1, 1)))
|
|
350
|
+
|
|
351
|
+
expect(
|
|
352
|
+
Siwe.createMessage({
|
|
353
|
+
...message,
|
|
354
|
+
resources: ['https://example.com', 'https://example.com/foo'],
|
|
355
|
+
}),
|
|
356
|
+
).toMatchInlineSnapshot(`
|
|
357
|
+
"example.com wants you to sign in with your Ethereum account:
|
|
358
|
+
0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
URI: https://example.com/path
|
|
362
|
+
Version: 1
|
|
363
|
+
Chain ID: 1
|
|
364
|
+
Nonce: foobarbaz
|
|
365
|
+
Issued At: 2023-02-01T00:00:00.000Z
|
|
366
|
+
Resources:
|
|
367
|
+
- https://example.com
|
|
368
|
+
- https://example.com/foo"
|
|
369
|
+
`)
|
|
370
|
+
|
|
371
|
+
vi.useRealTimers()
|
|
372
|
+
})
|
|
373
|
+
|
|
327
374
|
test('behavior: invalid version', () => {
|
|
328
375
|
expect(() =>
|
|
329
376
|
// @ts-expect-error
|
|
@@ -373,7 +420,7 @@ describe('createMessage', () => {
|
|
|
373
420
|
- Every resource must be a RFC 3986 URI.
|
|
374
421
|
- See https://www.rfc-editor.org/rfc/rfc3986
|
|
375
422
|
|
|
376
|
-
Provided value:
|
|
423
|
+
Provided value: foo]
|
|
377
424
|
`)
|
|
378
425
|
})
|
|
379
426
|
|
|
@@ -426,6 +473,58 @@ describe('isUri', () => {
|
|
|
426
473
|
)
|
|
427
474
|
})
|
|
428
475
|
|
|
476
|
+
test('behavior: authority with empty path (bare origin)', () => {
|
|
477
|
+
expect(Siwe.isUri('https://example.com')).toMatchInlineSnapshot(
|
|
478
|
+
`"https://example.com"`,
|
|
479
|
+
)
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
test('behavior: authority with empty path and port', () => {
|
|
483
|
+
expect(Siwe.isUri('https://example.com:8080')).toMatchInlineSnapshot(
|
|
484
|
+
`"https://example.com:8080"`,
|
|
485
|
+
)
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
test('behavior: authority with empty path and query', () => {
|
|
489
|
+
expect(Siwe.isUri('https://example.com?a=1')).toMatchInlineSnapshot(
|
|
490
|
+
`"https://example.com?a=1"`,
|
|
491
|
+
)
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
test('behavior: authority with empty path and fragment', () => {
|
|
495
|
+
expect(Siwe.isUri('https://example.com#top')).toMatchInlineSnapshot(
|
|
496
|
+
`"https://example.com#top"`,
|
|
497
|
+
)
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
test('behavior: authority with empty path (non-http scheme)', () => {
|
|
501
|
+
expect(Siwe.isUri('ipfs://bafybeiabc')).toMatchInlineSnapshot(
|
|
502
|
+
`"ipfs://bafybeiabc"`,
|
|
503
|
+
)
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
test('behavior: authority variants with empty path', () => {
|
|
507
|
+
expect([
|
|
508
|
+
Siwe.isUri('https://[2001:db8::1]'),
|
|
509
|
+
Siwe.isUri('https://user:pass@example.com:8080'),
|
|
510
|
+
Siwe.isUri('foo://[v1.fe]'),
|
|
511
|
+
]).toMatchInlineSnapshot(`
|
|
512
|
+
[
|
|
513
|
+
"https://[2001:db8::1]",
|
|
514
|
+
"https://user:pass@example.com:8080",
|
|
515
|
+
"foo://[v1.fe]",
|
|
516
|
+
]
|
|
517
|
+
`)
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
test.each([
|
|
521
|
+
'https://example.com:bad',
|
|
522
|
+
'https://[:::]',
|
|
523
|
+
'https://user@@example.com',
|
|
524
|
+
])('behavior: invalid authority with empty path `%s`', (uri) => {
|
|
525
|
+
expect(Siwe.isUri(uri)).toMatchInlineSnapshot(`false`)
|
|
526
|
+
})
|
|
527
|
+
|
|
429
528
|
test('behavior: check for illegal characters', () => {
|
|
430
529
|
expect(Siwe.isUri('^')).toBeFalsy()
|
|
431
530
|
})
|
|
@@ -348,6 +348,8 @@ describe('fromRpc', () => {
|
|
|
348
348
|
type: 'p256',
|
|
349
349
|
},
|
|
350
350
|
})
|
|
351
|
+
if (typeof result.signature === 'string')
|
|
352
|
+
throw new Error('Expected structured primitive signature')
|
|
351
353
|
expect(result.signature.type).toBe('p256')
|
|
352
354
|
expect(result.signature).toHaveProperty('prehash', true)
|
|
353
355
|
expect(result.signature).toHaveProperty('publicKey')
|
|
@@ -370,6 +372,8 @@ describe('fromRpc', () => {
|
|
|
370
372
|
'0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976305000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22222c226f726967696e223a2268747470733a2f2f6578616d706c652e636f6d222c2263726f73734f726967696e223a66616c73657d',
|
|
371
373
|
},
|
|
372
374
|
})
|
|
375
|
+
if (typeof result.signature === 'string')
|
|
376
|
+
throw new Error('Expected structured primitive signature')
|
|
373
377
|
expect(result.signature.type).toBe('webAuthn')
|
|
374
378
|
expect(result.signature).toHaveProperty('metadata')
|
|
375
379
|
expect(result.signature).toHaveProperty('publicKey')
|
|
@@ -994,6 +998,8 @@ describe('toRpc', () => {
|
|
|
994
998
|
type: 'p256',
|
|
995
999
|
},
|
|
996
1000
|
})
|
|
1001
|
+
if (typeof result.signature === 'string')
|
|
1002
|
+
throw new Error('Expected structured primitive signature')
|
|
997
1003
|
expect(result.signature.type).toBe('p256')
|
|
998
1004
|
})
|
|
999
1005
|
|
|
@@ -1019,6 +1025,8 @@ describe('toRpc', () => {
|
|
|
1019
1025
|
type: 'webAuthn',
|
|
1020
1026
|
},
|
|
1021
1027
|
})
|
|
1028
|
+
if (typeof result.signature === 'string')
|
|
1029
|
+
throw new Error('Expected structured primitive signature')
|
|
1022
1030
|
expect(result.signature.type).toBe('webAuthn')
|
|
1023
1031
|
})
|
|
1024
1032
|
})
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { expectTypeOf, test } from 'vp/test'
|
|
2
2
|
import * as KeyAuthorization from './KeyAuthorization.js'
|
|
3
|
-
import * as
|
|
4
|
-
import type * as SignatureEnvelope from './SignatureEnvelope.js'
|
|
3
|
+
import * as SignatureEnvelope from './SignatureEnvelope.js'
|
|
5
4
|
|
|
6
5
|
const authorization = {
|
|
7
6
|
address: '0x1111111111111111111111111111111111111111',
|
|
@@ -20,26 +19,18 @@ const signature = {
|
|
|
20
19
|
|
|
21
20
|
const multisig = {
|
|
22
21
|
account: '0x2222222222222222222222222222222222222222',
|
|
23
|
-
config:
|
|
22
|
+
config: {
|
|
23
|
+
salt: `0x${'00'.repeat(32)}`,
|
|
24
|
+
version: 0n,
|
|
25
|
+
threshold: 1,
|
|
24
26
|
owners: [
|
|
25
|
-
{
|
|
26
|
-
owner: '0x1111111111111111111111111111111111111111',
|
|
27
|
-
weight: 1,
|
|
28
|
-
},
|
|
27
|
+
{ owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
29
28
|
],
|
|
30
|
-
|
|
31
|
-
version: 1n,
|
|
32
|
-
}),
|
|
29
|
+
},
|
|
33
30
|
signatures: [signature],
|
|
34
31
|
type: 'multisig',
|
|
35
32
|
} as const satisfies SignatureEnvelope.Multisig
|
|
36
33
|
|
|
37
|
-
const keychain = {
|
|
38
|
-
inner: signature,
|
|
39
|
-
type: 'keychain',
|
|
40
|
-
userAddress: authorization.address,
|
|
41
|
-
} as const satisfies SignatureEnvelope.Keychain
|
|
42
|
-
|
|
43
34
|
test('accepts primitive signatures', () => {
|
|
44
35
|
const signed = KeyAuthorization.from(authorization, { signature })
|
|
45
36
|
|
|
@@ -48,62 +39,13 @@ test('accepts primitive signatures', () => {
|
|
|
48
39
|
>()
|
|
49
40
|
})
|
|
50
41
|
|
|
51
|
-
test('accepts multisig signatures', () => {
|
|
52
|
-
const signed = KeyAuthorization.from(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
>()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
config: MultisigConfig.toRpc(multisig.config),
|
|
61
|
-
signatures: [
|
|
62
|
-
{
|
|
63
|
-
r: '0x01',
|
|
64
|
-
s: '0x02',
|
|
65
|
-
type: 'secp256k1',
|
|
66
|
-
yParity: '0x0',
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
} as const satisfies SignatureEnvelope.MultisigRpc
|
|
70
|
-
|
|
71
|
-
const rpc: KeyAuthorization.Rpc = {
|
|
72
|
-
chainId: '0x1',
|
|
73
|
-
expiry: null,
|
|
74
|
-
keyId: authorization.address,
|
|
75
|
-
keyType: authorization.type,
|
|
76
|
-
signature: multisigRpc,
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
expectTypeOf(rpc).toEqualTypeOf<KeyAuthorization.Rpc>()
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
test('rejects keychain signatures', () => {
|
|
83
|
-
KeyAuthorization.from(authorization, {
|
|
84
|
-
// @ts-expect-error Key authorizations do not accept keychain signatures.
|
|
85
|
-
signature: keychain,
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
const keychainRpc = {
|
|
89
|
-
signature: {
|
|
90
|
-
r: '0x01',
|
|
91
|
-
s: '0x02',
|
|
92
|
-
type: 'secp256k1',
|
|
93
|
-
yParity: '0x0',
|
|
94
|
-
},
|
|
95
|
-
type: 'keychain',
|
|
96
|
-
userAddress: authorization.address,
|
|
97
|
-
} as const satisfies SignatureEnvelope.KeychainRpc
|
|
98
|
-
|
|
99
|
-
const rpc: KeyAuthorization.Rpc = {
|
|
100
|
-
chainId: '0x1',
|
|
101
|
-
expiry: null,
|
|
102
|
-
keyId: authorization.address,
|
|
103
|
-
keyType: authorization.type,
|
|
104
|
-
// @ts-expect-error Key authorizations do not accept keychain RPC signatures.
|
|
105
|
-
signature: keychainRpc,
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
expectTypeOf(rpc).toEqualTypeOf<KeyAuthorization.Rpc>()
|
|
42
|
+
test('accepts multisig signatures and account-bound grants', () => {
|
|
43
|
+
const signed = KeyAuthorization.from(
|
|
44
|
+
{ ...authorization, account: multisig.account, type: 'multisig' },
|
|
45
|
+
{ signature: multisig },
|
|
46
|
+
)
|
|
47
|
+
expectTypeOf(signed).toMatchTypeOf<KeyAuthorization.Signed>()
|
|
48
|
+
expectTypeOf(
|
|
49
|
+
KeyAuthorization.toRpc(signed),
|
|
50
|
+
).toMatchTypeOf<KeyAuthorization.Rpc>()
|
|
109
51
|
})
|
|
@@ -69,6 +69,8 @@ const signature_multisig = {
|
|
|
69
69
|
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
70
70
|
type: 'multisig',
|
|
71
71
|
} as const satisfies SignatureEnvelope.Multisig
|
|
72
|
+
const signature_multisig_rpc =
|
|
73
|
+
'0xf89794be95c3f554e9fc85ec51be69a3d807a0d55bcf2cf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d694f39fd6e51aad88f6f4ce6ab8827279cfffb9226601f843b841fa78c5905fb0b9d6066ef531f962a62bc6ef0d5eb59ecb134056d206f75aaed7780926ff2601a935c2c79707d9e1799948c9f19dcdde1e090e903b19a07923d01c' as const
|
|
72
74
|
|
|
73
75
|
const signature_keychain = {
|
|
74
76
|
inner: SignatureEnvelope.from(signature_secp256k1),
|
|
@@ -76,7 +78,66 @@ const signature_keychain = {
|
|
|
76
78
|
userAddress: address,
|
|
77
79
|
} as const satisfies SignatureEnvelope.Keychain
|
|
78
80
|
|
|
81
|
+
describe('multisig account binding', () => {
|
|
82
|
+
test('rejects unbound grants before signing or encoding', () => {
|
|
83
|
+
const authorization = { address, chainId: 1n, type: 'multisig' } as const
|
|
84
|
+
const signed = {
|
|
85
|
+
...authorization,
|
|
86
|
+
signature: SignatureEnvelope.from(signature_secp256k1),
|
|
87
|
+
}
|
|
88
|
+
for (const encode of [
|
|
89
|
+
() => KeyAuthorization.from(authorization),
|
|
90
|
+
() => KeyAuthorization.toTuple(authorization),
|
|
91
|
+
() => KeyAuthorization.getSignPayload(authorization),
|
|
92
|
+
() => KeyAuthorization.serialize(authorization),
|
|
93
|
+
() => KeyAuthorization.toRpc(signed),
|
|
94
|
+
])
|
|
95
|
+
expect(encode).toThrowErrorMatchingInlineSnapshot(
|
|
96
|
+
`[KeyAuthorization.MissingAccountError: Multisig key grants require a parent account binding.]`,
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('rejects unbound RPC and RLP grants', () => {
|
|
101
|
+
const signed = KeyAuthorization.from(
|
|
102
|
+
{ account: address, address, chainId: 1n, type: 'multisig' },
|
|
103
|
+
{ signature: signature_secp256k1 },
|
|
104
|
+
)
|
|
105
|
+
const rpc = KeyAuthorization.toRpc(signed)
|
|
106
|
+
for (const account of [undefined, null])
|
|
107
|
+
expect(() => KeyAuthorization.fromRpc({ ...rpc, account })).toThrowError(
|
|
108
|
+
KeyAuthorization.MissingAccountError,
|
|
109
|
+
)
|
|
110
|
+
expect(() =>
|
|
111
|
+
KeyAuthorization.fromTuple([['0x01', '0x03', address]]),
|
|
112
|
+
).toThrowError(KeyAuthorization.MissingAccountError)
|
|
113
|
+
expect(KeyAuthorization.fromRpc(rpc).account).toBe(address)
|
|
114
|
+
expect(
|
|
115
|
+
KeyAuthorization.deserialize(KeyAuthorization.serialize(signed)).account,
|
|
116
|
+
).toBe(address)
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
79
120
|
describe('from', () => {
|
|
121
|
+
test('accepts multisig grants and delegates', () => {
|
|
122
|
+
const signed = KeyAuthorization.from(
|
|
123
|
+
{
|
|
124
|
+
address,
|
|
125
|
+
chainId: 1n,
|
|
126
|
+
type: 'multisig',
|
|
127
|
+
account: signature_multisig.account,
|
|
128
|
+
},
|
|
129
|
+
{ signature: signature_multisig },
|
|
130
|
+
)
|
|
131
|
+
expect(
|
|
132
|
+
KeyAuthorization.deserialize(KeyAuthorization.serialize(signed)),
|
|
133
|
+
).toEqual(signed)
|
|
134
|
+
expect(KeyAuthorization.fromRpc(KeyAuthorization.toRpc(signed))).toEqual(
|
|
135
|
+
signed,
|
|
136
|
+
)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('fromRpc', () => {
|
|
80
141
|
test('default', () => {
|
|
81
142
|
const authorization = KeyAuthorization.from({
|
|
82
143
|
address,
|
|
@@ -502,16 +563,42 @@ describe('from', () => {
|
|
|
502
563
|
})
|
|
503
564
|
|
|
504
565
|
describe('fromRpc', () => {
|
|
505
|
-
test('multisig', () => {
|
|
566
|
+
test('behavior: multisig', () => {
|
|
506
567
|
const authorization = KeyAuthorization.fromRpc({
|
|
507
568
|
chainId: '0x1',
|
|
508
569
|
expiry: null,
|
|
509
570
|
keyId: address,
|
|
510
571
|
keyType: 'secp256k1',
|
|
511
|
-
signature:
|
|
572
|
+
signature: signature_multisig_rpc,
|
|
512
573
|
})
|
|
513
574
|
|
|
514
|
-
expect(authorization.signature).
|
|
575
|
+
expect(authorization.signature).toMatchInlineSnapshot(`
|
|
576
|
+
{
|
|
577
|
+
"account": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c",
|
|
578
|
+
"config": {
|
|
579
|
+
"owners": [
|
|
580
|
+
{
|
|
581
|
+
"owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
|
|
582
|
+
"weight": 1,
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
586
|
+
"threshold": 1,
|
|
587
|
+
"version": 1n,
|
|
588
|
+
},
|
|
589
|
+
"signatures": [
|
|
590
|
+
{
|
|
591
|
+
"signature": {
|
|
592
|
+
"r": "0xfa78c5905fb0b9d6066ef531f962a62bc6ef0d5eb59ecb134056d206f75aaed7",
|
|
593
|
+
"s": "0x780926ff2601a935c2c79707d9e1799948c9f19dcdde1e090e903b19a07923d0",
|
|
594
|
+
"yParity": 1,
|
|
595
|
+
},
|
|
596
|
+
"type": "secp256k1",
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
"type": "multisig",
|
|
600
|
+
}
|
|
601
|
+
`)
|
|
515
602
|
})
|
|
516
603
|
|
|
517
604
|
test('rejects a keychain signature', () => {
|
|
@@ -1084,17 +1171,16 @@ describe('getSignPayload', () => {
|
|
|
1084
1171
|
describe('deserialize', () => {
|
|
1085
1172
|
test('example: matches the frozen multisig witness vector', () => {
|
|
1086
1173
|
const serialized =
|
|
1087
|
-
'
|
|
1174
|
+
'0xf8f1f85382107980941eff47bc3a10a45d4b230b5d10e37751fe6aa718808080a053535353535353535353535353535353535353535353535353535353535353538094c4a590afa7337e5cd5eb3aa60cacf91c5400044bb89a05f89794c4a590afa7337e5cd5eb3aa60cacf91c5400044bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b8412cfed9350faf80e4115e3c4c967356b80a8e6ee42e4a49ab953c8c8a3537d8d8359bf035a9bea18f32b0da7129cdce3ce701abc09ad8919b93da21a0d57e21451b' as const
|
|
1088
1175
|
const authorization = KeyAuthorization.deserialize(serialized)
|
|
1089
1176
|
|
|
1090
1177
|
expect(authorization).toMatchInlineSnapshot(`
|
|
1091
1178
|
{
|
|
1092
|
-
"account": "
|
|
1179
|
+
"account": "0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b",
|
|
1093
1180
|
"address": "0x1eff47bc3a10a45d4b230b5d10e37751fe6aa718",
|
|
1094
1181
|
"chainId": 4217n,
|
|
1095
|
-
"isAdmin": false,
|
|
1096
1182
|
"signature": {
|
|
1097
|
-
"account": "
|
|
1183
|
+
"account": "0xc4a590afa7337e5cd5eb3aa60cacf91c5400044b",
|
|
1098
1184
|
"config": {
|
|
1099
1185
|
"owners": [
|
|
1100
1186
|
{
|
|
@@ -1306,6 +1392,19 @@ describe('serialize', () => {
|
|
|
1306
1392
|
})
|
|
1307
1393
|
|
|
1308
1394
|
describe('toRpc', () => {
|
|
1395
|
+
test('behavior: multisig', () => {
|
|
1396
|
+
const authorization = KeyAuthorization.toRpc({
|
|
1397
|
+
address,
|
|
1398
|
+
chainId: 1n,
|
|
1399
|
+
signature: signature_multisig,
|
|
1400
|
+
type: 'secp256k1',
|
|
1401
|
+
})
|
|
1402
|
+
|
|
1403
|
+
expect(authorization.signature).toMatchInlineSnapshot(
|
|
1404
|
+
`"${signature_multisig_rpc}"`,
|
|
1405
|
+
)
|
|
1406
|
+
})
|
|
1407
|
+
|
|
1309
1408
|
test('secp256k1', () => {
|
|
1310
1409
|
const authorization = KeyAuthorization.from({
|
|
1311
1410
|
address,
|
|
@@ -2503,11 +2602,11 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2503
2602
|
expect((authTuple as unknown as unknown[]).length).toBe(3)
|
|
2504
2603
|
})
|
|
2505
2604
|
|
|
2506
|
-
test('fromTuple:
|
|
2605
|
+
test('fromTuple: preserves isAdmin without account', () => {
|
|
2507
2606
|
const restored = KeyAuthorization.fromTuple([
|
|
2508
2607
|
['0x01', '0x', address, '0x', [], [], '0x', '0x01'],
|
|
2509
2608
|
])
|
|
2510
|
-
expect(restored.isAdmin).
|
|
2609
|
+
expect(restored.isAdmin).toBe(true)
|
|
2511
2610
|
expect(restored.account).toBeUndefined()
|
|
2512
2611
|
})
|
|
2513
2612
|
|
|
@@ -2515,7 +2614,7 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2515
2614
|
const restored = KeyAuthorization.fromTuple([
|
|
2516
2615
|
['0x01', '0x', address, '0x', [], [], '0x', '0x', account],
|
|
2517
2616
|
])
|
|
2518
|
-
expect(restored.isAdmin).
|
|
2617
|
+
expect(restored.isAdmin).toBeUndefined()
|
|
2519
2618
|
expect(restored.account).toBe(account)
|
|
2520
2619
|
})
|
|
2521
2620
|
|
|
@@ -2561,7 +2660,7 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2561
2660
|
})
|
|
2562
2661
|
const serialized = KeyAuthorization.serialize(authorization)
|
|
2563
2662
|
const restored = KeyAuthorization.deserialize(serialized)
|
|
2564
|
-
expect(restored.isAdmin).
|
|
2663
|
+
expect(restored.isAdmin).toBeUndefined()
|
|
2565
2664
|
expect(restored.account).toBe(account)
|
|
2566
2665
|
})
|
|
2567
2666
|
|
|
@@ -2584,14 +2683,14 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2584
2683
|
expect(restored.account).toBe(account)
|
|
2585
2684
|
})
|
|
2586
2685
|
|
|
2587
|
-
test('fromRpc:
|
|
2686
|
+
test('fromRpc: preserves isAdmin without account', () => {
|
|
2588
2687
|
const authorization = KeyAuthorization.from(
|
|
2589
2688
|
{ address, chainId: 1n, type: 'secp256k1' },
|
|
2590
2689
|
{ signature: SignatureEnvelope.from(signature_secp256k1) },
|
|
2591
2690
|
)
|
|
2592
2691
|
const rpc = KeyAuthorization.toRpc(authorization)
|
|
2593
2692
|
const restored = KeyAuthorization.fromRpc({ ...rpc, isAdmin: true })
|
|
2594
|
-
expect(restored.isAdmin).
|
|
2693
|
+
expect(restored.isAdmin).toBe(true)
|
|
2595
2694
|
expect(restored.account).toBeUndefined()
|
|
2596
2695
|
})
|
|
2597
2696
|
|
|
@@ -2602,7 +2701,7 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2602
2701
|
)
|
|
2603
2702
|
const rpc = KeyAuthorization.toRpc(authorization)
|
|
2604
2703
|
const restored = KeyAuthorization.fromRpc({ ...rpc, account })
|
|
2605
|
-
expect(restored.isAdmin).
|
|
2704
|
+
expect(restored.isAdmin).toBeUndefined()
|
|
2606
2705
|
expect(restored.account).toBe(account)
|
|
2607
2706
|
})
|
|
2608
2707
|
|