ox 0.14.28 → 0.14.30

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.
@@ -3,6 +3,7 @@ import {
3
3
  Hex,
4
4
  P256,
5
5
  PublicKey,
6
+ Rlp,
6
7
  Secp256k1,
7
8
  Signature,
8
9
  WebAuthnP256,
@@ -1097,6 +1098,76 @@ describe('from', () => {
1097
1098
  expect(envelope.keyId).toBe(explicitKeyId)
1098
1099
  })
1099
1100
  })
1101
+
1102
+ describe('multisig', () => {
1103
+ const genesisConfig = MultisigConfig.from({
1104
+ threshold: 1,
1105
+ owners: [
1106
+ {
1107
+ owner: '0x1111111111111111111111111111111111111111',
1108
+ weight: 1,
1109
+ },
1110
+ ],
1111
+ })
1112
+
1113
+ test('behavior: derives `account` from `genesisConfig`', () => {
1114
+ const envelope = SignatureEnvelope.from({
1115
+ genesisConfig,
1116
+ signatures: [SignatureEnvelope.from(signature_secp256k1)],
1117
+ })
1118
+
1119
+ expect(envelope).toMatchObject({
1120
+ type: 'multisig',
1121
+ account: MultisigConfig.getAddress(genesisConfig),
1122
+ })
1123
+ expect('genesisConfig' in envelope).toBe(false)
1124
+ expect((envelope as SignatureEnvelope.Multisig).init).toBeUndefined()
1125
+ })
1126
+
1127
+ test('behavior: `init: true` opts into bootstrap (uses `genesisConfig` as `init`)', () => {
1128
+ const envelope = SignatureEnvelope.from({
1129
+ genesisConfig,
1130
+ signatures: [SignatureEnvelope.from(signature_secp256k1)],
1131
+ init: true,
1132
+ })
1133
+
1134
+ expect((envelope as SignatureEnvelope.Multisig).init).toEqual(
1135
+ genesisConfig,
1136
+ )
1137
+ })
1138
+
1139
+ test('behavior: `init` accepts an explicit config', () => {
1140
+ const otherConfig = MultisigConfig.from({
1141
+ threshold: 1,
1142
+ owners: [
1143
+ {
1144
+ owner: '0x2222222222222222222222222222222222222222',
1145
+ weight: 1,
1146
+ },
1147
+ ],
1148
+ })
1149
+ const envelope = SignatureEnvelope.from({
1150
+ genesisConfig,
1151
+ signatures: [SignatureEnvelope.from(signature_secp256k1)],
1152
+ init: otherConfig,
1153
+ })
1154
+
1155
+ expect((envelope as SignatureEnvelope.Multisig).init).toEqual(otherConfig)
1156
+ })
1157
+
1158
+ test('behavior: `{ account }` form still works', () => {
1159
+ const account = MultisigConfig.getAddress(genesisConfig)
1160
+ const envelope = SignatureEnvelope.from({
1161
+ account,
1162
+ signatures: [SignatureEnvelope.from(signature_secp256k1)],
1163
+ })
1164
+
1165
+ expect(envelope).toMatchObject({
1166
+ type: 'multisig',
1167
+ account,
1168
+ })
1169
+ })
1170
+ })
1100
1171
  })
1101
1172
 
1102
1173
  describe('getType', () => {
@@ -1698,33 +1769,40 @@ describe('serialize', () => {
1698
1769
  })
1699
1770
 
1700
1771
  describe('sortMultisigApprovals', () => {
1701
- const account = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as const
1702
- const configId = `0x${'11'.repeat(32)}` as const
1703
- const payload = `0x${'42'.repeat(32)}` as const
1704
- const digest = MultisigConfig.getSignPayload({
1705
- account,
1706
- configId,
1707
- payload,
1708
- })
1709
-
1710
- const owners = Array.from({ length: 3 }, () => {
1772
+ // Build owner key pairs first so we can construct a real genesis config
1773
+ // whose owner set matches the keys that produce the approvals below.
1774
+ const ownerKeys = Array.from({ length: 3 }, () => {
1711
1775
  const privateKey = Secp256k1.randomPrivateKey()
1712
1776
  const address = Address.fromPublicKey(
1713
1777
  Secp256k1.getPublicKey({ privateKey }),
1714
1778
  )
1715
- const signature = SignatureEnvelope.from(
1716
- Secp256k1.sign({ payload: digest, privateKey }),
1717
- )
1718
- return { address, signature } as const
1779
+ return { address, privateKey } as const
1719
1780
  })
1720
- const ascending = [...owners].sort((a, b) =>
1781
+ const ascendingOwners = [...ownerKeys].sort((a, b) =>
1721
1782
  Hex.toBigInt(a.address) < Hex.toBigInt(b.address) ? -1 : 1,
1722
1783
  )
1723
1784
 
1785
+ const genesisConfig = MultisigConfig.from({
1786
+ threshold: 2,
1787
+ owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
1788
+ })
1789
+ const payload = `0x${'42'.repeat(32)}` as const
1790
+ const digest = MultisigConfig.getSignPayload({ payload, genesisConfig })
1791
+
1792
+ const owners = ownerKeys.map((owner) => ({
1793
+ address: owner.address,
1794
+ signature: SignatureEnvelope.from(
1795
+ Secp256k1.sign({ payload: digest, privateKey: owner.privateKey }),
1796
+ ),
1797
+ }))
1798
+ const ascending = ascendingOwners.map((o) => ({
1799
+ address: o.address,
1800
+ signature: owners.find((x) => x.address === o.address)!.signature,
1801
+ }))
1802
+
1724
1803
  test('behavior: orders approvals ascending by recovered owner address', () => {
1725
1804
  const ordered = SignatureEnvelope.sortMultisigApprovals({
1726
- account,
1727
- configId,
1805
+ genesisConfig,
1728
1806
  payload,
1729
1807
  // Provide approvals in reverse of the canonical order.
1730
1808
  signatures: [...ascending].reverse().map((owner) => owner.signature),
@@ -1736,8 +1814,7 @@ describe('sortMultisigApprovals', () => {
1736
1814
  const signatures = ascending.map((owner) => owner.signature)
1737
1815
  expect(
1738
1816
  SignatureEnvelope.sortMultisigApprovals({
1739
- account,
1740
- configId,
1817
+ genesisConfig,
1741
1818
  payload,
1742
1819
  signatures,
1743
1820
  }),
@@ -1746,8 +1823,7 @@ describe('sortMultisigApprovals', () => {
1746
1823
 
1747
1824
  test('behavior: recovered order matches the config owner order', () => {
1748
1825
  const ordered = SignatureEnvelope.sortMultisigApprovals({
1749
- account,
1750
- configId,
1826
+ genesisConfig,
1751
1827
  payload,
1752
1828
  signatures: owners.map((owner) => owner.signature),
1753
1829
  })
@@ -1756,6 +1832,23 @@ describe('sortMultisigApprovals', () => {
1756
1832
  )
1757
1833
  expect(recovered).toEqual(ascending.map((owner) => owner.address))
1758
1834
  })
1835
+
1836
+ test('behavior: `genesisConfig` and `{ account }` produce identical ordering', () => {
1837
+ const account = MultisigConfig.getAddress(genesisConfig)
1838
+ const signatures = owners.map((owner) => owner.signature)
1839
+
1840
+ const fromConfig = SignatureEnvelope.sortMultisigApprovals({
1841
+ genesisConfig,
1842
+ payload,
1843
+ signatures,
1844
+ })
1845
+ const fromAccount = SignatureEnvelope.sortMultisigApprovals({
1846
+ account,
1847
+ payload,
1848
+ signatures,
1849
+ })
1850
+ expect(fromConfig).toEqual(fromAccount)
1851
+ })
1759
1852
  })
1760
1853
 
1761
1854
  describe('validate', () => {
@@ -2757,8 +2850,6 @@ describe('CoercionError', () => {
2757
2850
 
2758
2851
  describe('multisig', () => {
2759
2852
  const account = '0x8ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7ae'
2760
- const configId =
2761
- '0x01781fe551182476f2422c759e82d81c92e3263737afbbad57def6e8b69d21f5'
2762
2853
 
2763
2854
  // P256 signatures do not carry `yParity` in the wire format, so use a clean
2764
2855
  // inner signature for round-trip equality checks.
@@ -2771,7 +2862,6 @@ describe('multisig', () => {
2771
2862
  const envelope = SignatureEnvelope.from({
2772
2863
  type: 'multisig',
2773
2864
  account,
2774
- configId,
2775
2865
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2776
2866
  })
2777
2867
 
@@ -2780,11 +2870,32 @@ describe('multisig', () => {
2780
2870
  expect(serialized.startsWith('0x05')).toBe(true)
2781
2871
  })
2782
2872
 
2873
+ test('serialize: wire shape is `0x05 || rlp([account, signatures])`', () => {
2874
+ const deterministic = SignatureEnvelope.from({
2875
+ type: 'multisig',
2876
+ account,
2877
+ signatures: [innerP256],
2878
+ })
2879
+ expect(SignatureEnvelope.serialize(deterministic)).toMatchInlineSnapshot(
2880
+ `"0x05f89b948ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7aef884b88201ccbb3485d4726235f13cb15ef394fb7158179fb7b1925eccec0147671090c52e77c3c53373cc1e3b05e7c23f609deb17cea8fe097300c45411237e9fe4166b35ad8ac16e167d6992c3e120d7f17d2376bc1cbcf30c46ba6dd00ce07303e742f511edf6ce1c32de66846f56afa7be1cbd729bc35750b6d0cdcf3ec9d75461aba001"`,
2881
+ )
2882
+ })
2883
+
2783
2884
  test('serialize/deserialize round-trip', () => {
2784
2885
  const serialized = SignatureEnvelope.serialize(envelope)
2785
2886
  expect(SignatureEnvelope.deserialize(serialized)).toEqual(envelope)
2786
2887
  })
2787
2888
 
2889
+ test('deserialize: rejects the empty `init` placeholder (`0x80`)', () => {
2890
+ const withPlaceholder = Hex.concat(
2891
+ '0x05',
2892
+ Rlp.fromHex([account, [SignatureEnvelope.serialize(innerP256)], '0x']),
2893
+ )
2894
+ expect(() => SignatureEnvelope.deserialize(withPlaceholder)).toThrowError(
2895
+ SignatureEnvelope.InvalidSerializedError,
2896
+ )
2897
+ })
2898
+
2788
2899
  test('getType', () => {
2789
2900
  expect(SignatureEnvelope.getType(envelope)).toBe('multisig')
2790
2901
  })
@@ -2810,6 +2921,97 @@ describe('multisig', () => {
2810
2921
  ).toThrowError()
2811
2922
  })
2812
2923
 
2924
+ test('assert: rejects keychain owner approvals', () => {
2925
+ expect(() =>
2926
+ SignatureEnvelope.assert({
2927
+ type: 'multisig',
2928
+ account,
2929
+ signatures: [signature_keychain_secp256k1],
2930
+ } as never),
2931
+ ).toThrowErrorMatchingInlineSnapshot(
2932
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: keychain owner approvals are not allowed.]`,
2933
+ )
2934
+ })
2935
+
2936
+ describe('nested approvals', () => {
2937
+ const nestedAccount = '0x1111111111111111111111111111111111111111'
2938
+ const nested = SignatureEnvelope.from({
2939
+ type: 'multisig',
2940
+ account: nestedAccount,
2941
+ signatures: [innerP256],
2942
+ })
2943
+
2944
+ test('serialize/deserialize round-trip with a nested multisig approval', () => {
2945
+ const parent = SignatureEnvelope.from({
2946
+ type: 'multisig',
2947
+ account,
2948
+ signatures: [innerP256, nested],
2949
+ })
2950
+ const serialized = SignatureEnvelope.serialize(parent)
2951
+ expect(SignatureEnvelope.deserialize(serialized)).toEqual(parent)
2952
+ })
2953
+
2954
+ test('assert: accepts the maximum nesting depth', () => {
2955
+ const depth3 = SignatureEnvelope.from({
2956
+ type: 'multisig',
2957
+ account,
2958
+ signatures: [
2959
+ SignatureEnvelope.from({
2960
+ type: 'multisig',
2961
+ account: nestedAccount,
2962
+ signatures: [nested],
2963
+ }),
2964
+ ],
2965
+ })
2966
+ expect(() => SignatureEnvelope.assert(depth3)).not.toThrowError()
2967
+ })
2968
+
2969
+ test('assert: rejects nesting deeper than the maximum', () => {
2970
+ const depth4 = SignatureEnvelope.from({
2971
+ type: 'multisig',
2972
+ account,
2973
+ signatures: [
2974
+ SignatureEnvelope.from({
2975
+ type: 'multisig',
2976
+ account: nestedAccount,
2977
+ signatures: [
2978
+ SignatureEnvelope.from({
2979
+ type: 'multisig',
2980
+ account: nestedAccount,
2981
+ signatures: [nested],
2982
+ }),
2983
+ ],
2984
+ }),
2985
+ ],
2986
+ })
2987
+ expect(() =>
2988
+ SignatureEnvelope.assert(depth4),
2989
+ ).toThrowErrorMatchingInlineSnapshot(
2990
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 3.]`,
2991
+ )
2992
+ })
2993
+
2994
+ test('assert: rejects nested approvals carrying `init`', () => {
2995
+ expect(() =>
2996
+ SignatureEnvelope.assert({
2997
+ type: 'multisig',
2998
+ account,
2999
+ signatures: [
3000
+ {
3001
+ ...nested,
3002
+ init: {
3003
+ threshold: 1,
3004
+ owners: [{ owner: nestedAccount, weight: 1 }],
3005
+ },
3006
+ },
3007
+ ],
3008
+ } as never),
3009
+ ).toThrowErrorMatchingInlineSnapshot(
3010
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: nested multisig owner approvals cannot carry \`init\`.]`,
3011
+ )
3012
+ })
3013
+ })
3014
+
2813
3015
  describe('init (bootstrap)', () => {
2814
3016
  const init = {
2815
3017
  salt: `0x${'00'.repeat(32)}` as const,
@@ -2825,7 +3027,6 @@ describe('multisig', () => {
2825
3027
  const bootstrapEnvelope = SignatureEnvelope.from({
2826
3028
  type: 'multisig',
2827
3029
  account,
2828
- configId,
2829
3030
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2830
3031
  init,
2831
3032
  })
@@ -2841,7 +3042,6 @@ describe('multisig', () => {
2841
3042
  const salted = SignatureEnvelope.from({
2842
3043
  type: 'multisig',
2843
3044
  account,
2844
- configId,
2845
3045
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2846
3046
  init: { ...init, salt: `0x${'42'.repeat(32)}` },
2847
3047
  })
@@ -2895,7 +3095,6 @@ describe('multisig', () => {
2895
3095
  SignatureEnvelope.assert({
2896
3096
  type: 'multisig',
2897
3097
  account,
2898
- configId,
2899
3098
  signatures: [],
2900
3099
  init: { threshold: 1, owners: [] },
2901
3100
  } as never),