ox 0.14.38 → 0.14.39
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 +14 -0
- package/_cjs/tempo/MultisigConfig.js +71 -15
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/MultisigOperation.js +42 -36
- package/_cjs/tempo/MultisigOperation.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +71 -108
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/MultisigConfig.js +140 -71
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/MultisigOperation.js +42 -37
- package/_esm/tempo/MultisigOperation.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +84 -143
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +1 -1
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigConfig.d.ts +137 -73
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/MultisigOperation.d.ts +1 -1
- package/_types/tempo/MultisigOperation.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +81 -75
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/package.json +6 -1
- package/tempo/KeyAuthorization.test-d.ts +12 -0
- package/tempo/KeyAuthorization.test.ts +54 -0
- package/tempo/KeyAuthorization.ts +1 -1
- package/tempo/MultisigConfig.test-d/package.json +6 -0
- package/tempo/MultisigConfig.test-d.ts +52 -0
- package/tempo/MultisigConfig.test.ts +291 -263
- package/tempo/MultisigConfig.ts +250 -95
- package/tempo/MultisigOperation.test-d.ts +6 -0
- package/tempo/MultisigOperation.test.ts +113 -72
- package/tempo/MultisigOperation.ts +41 -35
- package/tempo/SignatureEnvelope.test-d.ts +57 -40
- package/tempo/SignatureEnvelope.test.ts +523 -480
- package/tempo/SignatureEnvelope.ts +250 -257
- package/tempo/multisig.e2e.test.ts +113 -86
- package/version.ts +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Address,
|
|
3
|
+
Hash,
|
|
3
4
|
Hex,
|
|
4
5
|
P256,
|
|
5
6
|
PublicKey,
|
|
@@ -1100,72 +1101,86 @@ describe('from', () => {
|
|
|
1100
1101
|
})
|
|
1101
1102
|
|
|
1102
1103
|
describe('multisig', () => {
|
|
1103
|
-
const
|
|
1104
|
-
threshold: 1,
|
|
1104
|
+
const config = MultisigConfig.from({
|
|
1105
1105
|
owners: [
|
|
1106
1106
|
{
|
|
1107
1107
|
owner: '0x1111111111111111111111111111111111111111',
|
|
1108
1108
|
weight: 1,
|
|
1109
1109
|
},
|
|
1110
1110
|
],
|
|
1111
|
+
threshold: 1,
|
|
1111
1112
|
})
|
|
1112
1113
|
|
|
1113
|
-
test('behavior: derives
|
|
1114
|
-
const envelope = SignatureEnvelope.from({
|
|
1115
|
-
initialConfig,
|
|
1116
|
-
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1117
|
-
})
|
|
1118
|
-
|
|
1119
|
-
expect(envelope).toMatchObject({
|
|
1120
|
-
type: 'multisig',
|
|
1121
|
-
account: MultisigConfig.getAddress(initialConfig),
|
|
1122
|
-
})
|
|
1123
|
-
expect('initialConfig' in envelope).toBe(false)
|
|
1124
|
-
expect((envelope as SignatureEnvelope.Multisig).init).toBeUndefined()
|
|
1125
|
-
})
|
|
1126
|
-
|
|
1127
|
-
test('behavior: `init: true` opts into bootstrap (uses `initialConfig` as `init`)', () => {
|
|
1114
|
+
test('behavior: derives an initial account from config', () => {
|
|
1128
1115
|
const envelope = SignatureEnvelope.from({
|
|
1129
|
-
|
|
1116
|
+
config,
|
|
1130
1117
|
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1131
|
-
init: true,
|
|
1132
1118
|
})
|
|
1133
1119
|
|
|
1134
|
-
expect(
|
|
1135
|
-
|
|
1120
|
+
expect(envelope).toMatchInlineSnapshot(
|
|
1121
|
+
{
|
|
1122
|
+
signatures: [
|
|
1123
|
+
{
|
|
1124
|
+
signature: {
|
|
1125
|
+
r: expect.anything(),
|
|
1126
|
+
s: expect.anything(),
|
|
1127
|
+
yParity: expect.any(Number),
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
],
|
|
1131
|
+
},
|
|
1132
|
+
`
|
|
1133
|
+
{
|
|
1134
|
+
"account": "0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da",
|
|
1135
|
+
"config": {
|
|
1136
|
+
"owners": [
|
|
1137
|
+
{
|
|
1138
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
1139
|
+
"weight": 1,
|
|
1140
|
+
},
|
|
1141
|
+
],
|
|
1142
|
+
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
1143
|
+
"threshold": 1,
|
|
1144
|
+
"version": 0n,
|
|
1145
|
+
},
|
|
1146
|
+
"signatures": [
|
|
1147
|
+
{
|
|
1148
|
+
"signature": {
|
|
1149
|
+
"r": Anything,
|
|
1150
|
+
"s": Anything,
|
|
1151
|
+
"yParity": Any<Number>,
|
|
1152
|
+
},
|
|
1153
|
+
"type": "secp256k1",
|
|
1154
|
+
},
|
|
1155
|
+
],
|
|
1156
|
+
"type": "multisig",
|
|
1157
|
+
}
|
|
1158
|
+
`,
|
|
1136
1159
|
)
|
|
1137
1160
|
})
|
|
1138
1161
|
|
|
1139
|
-
test('behavior:
|
|
1140
|
-
const otherConfig = MultisigConfig.from({
|
|
1141
|
-
threshold: 1,
|
|
1142
|
-
owners: [
|
|
1143
|
-
{
|
|
1144
|
-
owner: '0x2222222222222222222222222222222222222222',
|
|
1145
|
-
weight: 1,
|
|
1146
|
-
},
|
|
1147
|
-
],
|
|
1148
|
-
})
|
|
1162
|
+
test('behavior: accepts an explicit account for a current config', () => {
|
|
1149
1163
|
const envelope = SignatureEnvelope.from({
|
|
1150
|
-
|
|
1164
|
+
account: '0x2222222222222222222222222222222222222222',
|
|
1165
|
+
config: { ...config, version: 1 },
|
|
1151
1166
|
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1152
|
-
init: otherConfig,
|
|
1153
1167
|
})
|
|
1154
1168
|
|
|
1155
|
-
expect(
|
|
1169
|
+
expect(envelope.account).toBe(
|
|
1170
|
+
'0x2222222222222222222222222222222222222222',
|
|
1171
|
+
)
|
|
1172
|
+
expect(envelope.config.version).toBe(1n)
|
|
1156
1173
|
})
|
|
1157
1174
|
|
|
1158
|
-
test('
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
account,
|
|
1168
|
-
})
|
|
1175
|
+
test('error: requires an account for a current config', () => {
|
|
1176
|
+
expect(() =>
|
|
1177
|
+
SignatureEnvelope.from({
|
|
1178
|
+
config: { ...config, version: 1n },
|
|
1179
|
+
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1180
|
+
} as never),
|
|
1181
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
1182
|
+
`[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
|
|
1183
|
+
)
|
|
1169
1184
|
})
|
|
1170
1185
|
})
|
|
1171
1186
|
})
|
|
@@ -1769,8 +1784,6 @@ describe('serialize', () => {
|
|
|
1769
1784
|
})
|
|
1770
1785
|
|
|
1771
1786
|
describe('sortMultisigApprovals', () => {
|
|
1772
|
-
// Build owner key pairs first so we can construct a real initial config
|
|
1773
|
-
// whose owner set matches the keys that produce the approvals below.
|
|
1774
1787
|
const ownerKeys = Array.from({ length: 3 }, () => {
|
|
1775
1788
|
const privateKey = Secp256k1.randomPrivateKey()
|
|
1776
1789
|
const address = Address.fromPublicKey(
|
|
@@ -1782,14 +1795,16 @@ describe('sortMultisigApprovals', () => {
|
|
|
1782
1795
|
Hex.toBigInt(a.address) < Hex.toBigInt(b.address) ? -1 : 1,
|
|
1783
1796
|
)
|
|
1784
1797
|
|
|
1785
|
-
const
|
|
1786
|
-
threshold: 2,
|
|
1798
|
+
const config = MultisigConfig.from({
|
|
1787
1799
|
owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
|
|
1800
|
+
threshold: 2,
|
|
1788
1801
|
})
|
|
1802
|
+
const account = MultisigConfig.getAddress(config)
|
|
1789
1803
|
const payload = `0x${'42'.repeat(32)}` as const
|
|
1790
1804
|
const digest = MultisigConfig.getSignPayload({
|
|
1805
|
+
account,
|
|
1806
|
+
config,
|
|
1791
1807
|
payload,
|
|
1792
|
-
initialConfig,
|
|
1793
1808
|
})
|
|
1794
1809
|
|
|
1795
1810
|
const owners = ownerKeys.map((owner) => ({
|
|
@@ -1805,7 +1820,8 @@ describe('sortMultisigApprovals', () => {
|
|
|
1805
1820
|
|
|
1806
1821
|
test('behavior: orders approvals ascending by recovered owner address', () => {
|
|
1807
1822
|
const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1808
|
-
|
|
1823
|
+
account,
|
|
1824
|
+
config,
|
|
1809
1825
|
payload,
|
|
1810
1826
|
// Provide approvals in reverse of the canonical order.
|
|
1811
1827
|
signatures: [...ascending].reverse().map((owner) => owner.signature),
|
|
@@ -1817,7 +1833,8 @@ describe('sortMultisigApprovals', () => {
|
|
|
1817
1833
|
const signatures = ascending.map((owner) => owner.signature)
|
|
1818
1834
|
expect(
|
|
1819
1835
|
SignatureEnvelope.sortMultisigApprovals({
|
|
1820
|
-
|
|
1836
|
+
account,
|
|
1837
|
+
config,
|
|
1821
1838
|
payload,
|
|
1822
1839
|
signatures,
|
|
1823
1840
|
}),
|
|
@@ -1826,7 +1843,8 @@ describe('sortMultisigApprovals', () => {
|
|
|
1826
1843
|
|
|
1827
1844
|
test('behavior: recovered order matches the config owner order', () => {
|
|
1828
1845
|
const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1829
|
-
|
|
1846
|
+
account,
|
|
1847
|
+
config,
|
|
1830
1848
|
payload,
|
|
1831
1849
|
signatures: owners.map((owner) => owner.signature),
|
|
1832
1850
|
})
|
|
@@ -1835,23 +1853,6 @@ describe('sortMultisigApprovals', () => {
|
|
|
1835
1853
|
)
|
|
1836
1854
|
expect(recovered).toEqual(ascending.map((owner) => owner.address))
|
|
1837
1855
|
})
|
|
1838
|
-
|
|
1839
|
-
test('behavior: `initialConfig` and `{ account }` produce identical ordering', () => {
|
|
1840
|
-
const account = MultisigConfig.getAddress(initialConfig)
|
|
1841
|
-
const signatures = owners.map((owner) => owner.signature)
|
|
1842
|
-
|
|
1843
|
-
const fromConfig = SignatureEnvelope.sortMultisigApprovals({
|
|
1844
|
-
initialConfig,
|
|
1845
|
-
payload,
|
|
1846
|
-
signatures,
|
|
1847
|
-
})
|
|
1848
|
-
const fromAccount = SignatureEnvelope.sortMultisigApprovals({
|
|
1849
|
-
account,
|
|
1850
|
-
payload,
|
|
1851
|
-
signatures,
|
|
1852
|
-
})
|
|
1853
|
-
expect(fromConfig).toEqual(fromAccount)
|
|
1854
|
-
})
|
|
1855
1856
|
})
|
|
1856
1857
|
|
|
1857
1858
|
describe('validate', () => {
|
|
@@ -2852,146 +2853,371 @@ describe('CoercionError', () => {
|
|
|
2852
2853
|
})
|
|
2853
2854
|
|
|
2854
2855
|
describe('multisig', () => {
|
|
2855
|
-
const
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
const
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2856
|
+
const initial =
|
|
2857
|
+
'0x05f897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d6947e5f4552091a69125d5dfcb7b8c2659029395bdf01f843b841869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b' as const
|
|
2858
|
+
const current =
|
|
2859
|
+
'0x05f897949dba7f426b711d4893c11611eacf7cc334e7146bf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d6942b5ad5c4795c026514f8317c7a215e218dccd6cf01f843b8414a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e0504a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf51b' as const
|
|
2860
|
+
const nested =
|
|
2861
|
+
'0x05f8f0949969e2243075b27a8eab61009c59b77ab13b83f6f83ba033333333333333333333333333333333333333333333333333333333333333338001d7d6944f9f5b162a7464bcc260ce44d7ae0d935f9c583701f89cb89a05f897944f9f5b162a7464bcc260ce44d7ae0d935f9c5837f83ba022222222222222222222222222222222222222222222222222222222222222228001d7d6946813eb9362372eef6200f3b1dbc3f819671cba6901f843b841032aa6f3ea7b0b7069720d0f3891983c493d149326c5c957d864ed7371b8475e3e95325079b24491f4b6d68920e4b3bacd7c6094df6ed88b8674864ab559c8fb1b' as const
|
|
2862
|
+
|
|
2863
|
+
test('example: decodes and re-encodes the frozen initial witness', () => {
|
|
2864
|
+
const envelope = SignatureEnvelope.deserialize(initial)
|
|
2865
|
+
|
|
2866
|
+
expect(envelope).toMatchInlineSnapshot(`
|
|
2867
|
+
{
|
|
2868
|
+
"account": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
|
|
2869
|
+
"config": {
|
|
2870
|
+
"owners": [
|
|
2871
|
+
{
|
|
2872
|
+
"owner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
2873
|
+
"weight": 1,
|
|
2874
|
+
},
|
|
2875
|
+
],
|
|
2876
|
+
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
2877
|
+
"threshold": 1,
|
|
2878
|
+
"version": 0n,
|
|
2879
|
+
},
|
|
2880
|
+
"signatures": [
|
|
2881
|
+
{
|
|
2882
|
+
"signature": {
|
|
2883
|
+
"r": 60871800714128149016591846789173983752390955456021923556141688418770269698437n,
|
|
2884
|
+
"s": 24367763726302312398084372528434045669788111037623467391175388828437596076276n,
|
|
2885
|
+
"yParity": 0,
|
|
2886
|
+
},
|
|
2887
|
+
"type": "secp256k1",
|
|
2888
|
+
},
|
|
2889
|
+
],
|
|
2890
|
+
"type": "multisig",
|
|
2891
|
+
}
|
|
2892
|
+
`)
|
|
2893
|
+
expect(SignatureEnvelope.serialize(envelope)).toBe(initial)
|
|
2863
2894
|
})
|
|
2864
2895
|
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2896
|
+
test('example: decodes and re-encodes the frozen current witness', () => {
|
|
2897
|
+
const envelope = SignatureEnvelope.deserialize(current)
|
|
2898
|
+
|
|
2899
|
+
expect(envelope).toMatchInlineSnapshot(`
|
|
2900
|
+
{
|
|
2901
|
+
"account": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
|
|
2902
|
+
"config": {
|
|
2903
|
+
"owners": [
|
|
2904
|
+
{
|
|
2905
|
+
"owner": "0x2b5ad5c4795c026514f8317c7a215e218dccd6cf",
|
|
2906
|
+
"weight": 1,
|
|
2907
|
+
},
|
|
2908
|
+
],
|
|
2909
|
+
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
2910
|
+
"threshold": 1,
|
|
2911
|
+
"version": 1n,
|
|
2912
|
+
},
|
|
2913
|
+
"signatures": [
|
|
2914
|
+
{
|
|
2915
|
+
"signature": {
|
|
2916
|
+
"r": 33496517174194049078979631673636743206216482049680398378774155205635729711184n,
|
|
2917
|
+
"s": 33572483501191170814542557897380275621900207658854593170435098846326627404789n,
|
|
2918
|
+
"yParity": 0,
|
|
2919
|
+
},
|
|
2920
|
+
"type": "secp256k1",
|
|
2921
|
+
},
|
|
2922
|
+
],
|
|
2923
|
+
"type": "multisig",
|
|
2924
|
+
}
|
|
2925
|
+
`)
|
|
2926
|
+
expect(SignatureEnvelope.serialize(envelope)).toBe(current)
|
|
2869
2927
|
})
|
|
2870
2928
|
|
|
2871
|
-
test('
|
|
2872
|
-
const
|
|
2873
|
-
|
|
2929
|
+
test('example: decodes and re-encodes the frozen nested witness', () => {
|
|
2930
|
+
const envelope = SignatureEnvelope.deserialize(nested)
|
|
2931
|
+
|
|
2932
|
+
expect(envelope).toMatchInlineSnapshot(`
|
|
2933
|
+
{
|
|
2934
|
+
"account": "0x9969e2243075b27a8eab61009c59b77ab13b83f6",
|
|
2935
|
+
"config": {
|
|
2936
|
+
"owners": [
|
|
2937
|
+
{
|
|
2938
|
+
"owner": "0x4f9f5b162a7464bcc260ce44d7ae0d935f9c5837",
|
|
2939
|
+
"weight": 1,
|
|
2940
|
+
},
|
|
2941
|
+
],
|
|
2942
|
+
"salt": "0x3333333333333333333333333333333333333333333333333333333333333333",
|
|
2943
|
+
"threshold": 1,
|
|
2944
|
+
"version": 0n,
|
|
2945
|
+
},
|
|
2946
|
+
"signatures": [
|
|
2947
|
+
{
|
|
2948
|
+
"account": "0x4f9f5b162a7464bcc260ce44d7ae0d935f9c5837",
|
|
2949
|
+
"config": {
|
|
2950
|
+
"owners": [
|
|
2951
|
+
{
|
|
2952
|
+
"owner": "0x6813eb9362372eef6200f3b1dbc3f819671cba69",
|
|
2953
|
+
"weight": 1,
|
|
2954
|
+
},
|
|
2955
|
+
],
|
|
2956
|
+
"salt": "0x2222222222222222222222222222222222222222222222222222222222222222",
|
|
2957
|
+
"threshold": 1,
|
|
2958
|
+
"version": 0n,
|
|
2959
|
+
},
|
|
2960
|
+
"signatures": [
|
|
2961
|
+
{
|
|
2962
|
+
"signature": {
|
|
2963
|
+
"r": 1432298388324792555676061659976215111517531547814803085200940835913455912798n,
|
|
2964
|
+
"s": 28307004081743690677157914067925034813425317008577473150162358055484664695035n,
|
|
2965
|
+
"yParity": 0,
|
|
2966
|
+
},
|
|
2967
|
+
"type": "secp256k1",
|
|
2968
|
+
},
|
|
2969
|
+
],
|
|
2970
|
+
"type": "multisig",
|
|
2971
|
+
},
|
|
2972
|
+
],
|
|
2973
|
+
"type": "multisig",
|
|
2974
|
+
}
|
|
2975
|
+
`)
|
|
2976
|
+
expect(SignatureEnvelope.serialize(envelope)).toBe(nested)
|
|
2874
2977
|
})
|
|
2875
2978
|
|
|
2876
|
-
test('
|
|
2877
|
-
const
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
)
|
|
2979
|
+
test('behavior: preserves exact 65-byte secp256k1 precedence', () => {
|
|
2980
|
+
const serialized = ('0x05' +
|
|
2981
|
+
'00'.repeat(31) +
|
|
2982
|
+
'00'.repeat(31) +
|
|
2983
|
+
'01' +
|
|
2984
|
+
'00') as Hex.Hex
|
|
2985
|
+
|
|
2986
|
+
expect(Hex.size(serialized)).toBe(65)
|
|
2987
|
+
expect(SignatureEnvelope.deserialize(serialized).type).toBe('secp256k1')
|
|
2885
2988
|
})
|
|
2886
2989
|
|
|
2887
|
-
test('
|
|
2990
|
+
test('behavior: serializes account, config, and approvals in wire order', () => {
|
|
2991
|
+
const envelope = SignatureEnvelope.deserialize(initial)
|
|
2992
|
+
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
2888
2993
|
const serialized = SignatureEnvelope.serialize(envelope)
|
|
2889
|
-
|
|
2994
|
+
const decoded = Rlp.toHex(Hex.slice(serialized, 1))
|
|
2995
|
+
|
|
2996
|
+
expect(decoded).toMatchInlineSnapshot(`
|
|
2997
|
+
[
|
|
2998
|
+
"0x9dba7f426b711d4893c11611eacf7cc334e7146b",
|
|
2999
|
+
[
|
|
3000
|
+
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3001
|
+
"0x",
|
|
3002
|
+
"0x01",
|
|
3003
|
+
[
|
|
3004
|
+
[
|
|
3005
|
+
"0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
3006
|
+
"0x01",
|
|
3007
|
+
],
|
|
3008
|
+
],
|
|
3009
|
+
],
|
|
3010
|
+
[
|
|
3011
|
+
"0x869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f98535dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f41b",
|
|
3012
|
+
],
|
|
3013
|
+
]
|
|
3014
|
+
`)
|
|
2890
3015
|
})
|
|
2891
3016
|
|
|
2892
|
-
test('
|
|
2893
|
-
const
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
}
|
|
2899
|
-
const legacy = Hex.concat(
|
|
2900
|
-
'0x05',
|
|
2901
|
-
Rlp.fromHex([account, [SignatureEnvelope.serialize(innerP256)], init]),
|
|
2902
|
-
)
|
|
2903
|
-
expect(() => SignatureEnvelope.deserialize(legacy)).toThrowError(
|
|
2904
|
-
SignatureEnvelope.InvalidSerializedError,
|
|
2905
|
-
)
|
|
2906
|
-
})
|
|
3017
|
+
test('behavior: round trips initial and current RPC witnesses', () => {
|
|
3018
|
+
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3019
|
+
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3020
|
+
const rpc = {
|
|
3021
|
+
current: SignatureEnvelope.toRpc(currentEnvelope),
|
|
3022
|
+
initial: SignatureEnvelope.toRpc(initialEnvelope),
|
|
3023
|
+
}
|
|
2907
3024
|
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
3025
|
+
expect(rpc).toMatchInlineSnapshot(`
|
|
3026
|
+
{
|
|
3027
|
+
"current": {
|
|
3028
|
+
"account": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
|
|
3029
|
+
"config": {
|
|
3030
|
+
"owners": [
|
|
3031
|
+
{
|
|
3032
|
+
"owner": "0x2b5ad5c4795c026514f8317c7a215e218dccd6cf",
|
|
3033
|
+
"weight": 1,
|
|
3034
|
+
},
|
|
3035
|
+
],
|
|
3036
|
+
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3037
|
+
"threshold": 1,
|
|
3038
|
+
"version": "0x1",
|
|
3039
|
+
},
|
|
3040
|
+
"signatures": [
|
|
3041
|
+
{
|
|
3042
|
+
"r": "0x4a0e5b5b4a90f08e6e8d676a73a22dc2cf022ffdcc9299512b5d9a6daf66e050",
|
|
3043
|
+
"s": "0x4a395a2ef6f6c0f173910b875c45d01aa66d928e56ba6f49bbc8186f80268bf5",
|
|
3044
|
+
"type": "secp256k1",
|
|
3045
|
+
"yParity": "0x0",
|
|
3046
|
+
},
|
|
3047
|
+
],
|
|
3048
|
+
},
|
|
3049
|
+
"initial": {
|
|
3050
|
+
"account": "0x9dba7f426b711d4893c11611eacf7cc334e7146b",
|
|
3051
|
+
"config": {
|
|
3052
|
+
"owners": [
|
|
3053
|
+
{
|
|
3054
|
+
"owner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
3055
|
+
"weight": 1,
|
|
3056
|
+
},
|
|
3057
|
+
],
|
|
3058
|
+
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3059
|
+
"threshold": 1,
|
|
3060
|
+
"version": "0x0",
|
|
3061
|
+
},
|
|
3062
|
+
"signatures": [
|
|
3063
|
+
{
|
|
3064
|
+
"r": "0x869437e01f64bebeb78a8a6b30bfd3a993819c8cad82c807515d9b9e9b36f985",
|
|
3065
|
+
"s": "0x35dfaa5eebc597715d05f6ce4927747f14fa4cd2acc717fdcd3877146437f8f4",
|
|
3066
|
+
"type": "secp256k1",
|
|
3067
|
+
"yParity": "0x0",
|
|
3068
|
+
},
|
|
3069
|
+
],
|
|
3070
|
+
},
|
|
3071
|
+
}
|
|
3072
|
+
`)
|
|
3073
|
+
expect(SignatureEnvelope.fromRpc(rpc.initial)).toStrictEqual(
|
|
3074
|
+
initialEnvelope,
|
|
2912
3075
|
)
|
|
2913
|
-
expect(
|
|
2914
|
-
|
|
3076
|
+
expect(SignatureEnvelope.fromRpc(rpc.current)).toStrictEqual(
|
|
3077
|
+
currentEnvelope,
|
|
2915
3078
|
)
|
|
2916
3079
|
})
|
|
2917
3080
|
|
|
2918
|
-
test('
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
3081
|
+
test('behavior: derives the account only for an initial witness', () => {
|
|
3082
|
+
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3083
|
+
if (initialEnvelope.type !== 'multisig') throw new Error('unreachable')
|
|
3084
|
+
if (initialEnvelope.config.version !== 0n) throw new Error('unreachable')
|
|
3085
|
+
const derived = SignatureEnvelope.from({
|
|
3086
|
+
config: initialEnvelope.config as MultisigConfig.Config<0n>,
|
|
3087
|
+
signatures: initialEnvelope.signatures,
|
|
3088
|
+
})
|
|
3089
|
+
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3090
|
+
if (currentEnvelope.type !== 'multisig') throw new Error('unreachable')
|
|
3091
|
+
|
|
3092
|
+
expect(derived.account).toBe(initialEnvelope.account)
|
|
3093
|
+
expect(() =>
|
|
3094
|
+
SignatureEnvelope.from({
|
|
3095
|
+
config: currentEnvelope.config,
|
|
3096
|
+
signatures: currentEnvelope.signatures,
|
|
2929
3097
|
} as never),
|
|
2930
|
-
).
|
|
3098
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
3099
|
+
`[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
|
|
3100
|
+
)
|
|
2931
3101
|
})
|
|
2932
3102
|
|
|
2933
|
-
test('
|
|
3103
|
+
test('behavior: extracts the multisig account', () => {
|
|
3104
|
+
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3105
|
+
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3106
|
+
|
|
2934
3107
|
expect(
|
|
2935
3108
|
SignatureEnvelope.extractAddress({
|
|
2936
3109
|
payload: '0xdeadbeef',
|
|
2937
3110
|
signature: envelope,
|
|
2938
3111
|
}),
|
|
2939
|
-
).toBe(account)
|
|
2940
|
-
})
|
|
2941
|
-
|
|
2942
|
-
test('
|
|
2943
|
-
const
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
3112
|
+
).toBe(envelope.account)
|
|
3113
|
+
})
|
|
3114
|
+
|
|
3115
|
+
test('example: matches the frozen eight-approval boundary vector', () => {
|
|
3116
|
+
const config = MultisigConfig.from({
|
|
3117
|
+
owners: Array.from({ length: 8 }, (_, index) => ({
|
|
3118
|
+
owner:
|
|
3119
|
+
`0x${(index + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
|
|
3120
|
+
weight: 1,
|
|
3121
|
+
})),
|
|
3122
|
+
salt: `0x${'66'.repeat(32)}`,
|
|
3123
|
+
threshold: 8,
|
|
3124
|
+
})
|
|
3125
|
+
const approval = SignatureEnvelope.from({
|
|
3126
|
+
r: 0x840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565n,
|
|
3127
|
+
s: 0x25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1n,
|
|
3128
|
+
yParity: 0,
|
|
3129
|
+
})
|
|
3130
|
+
const envelope = SignatureEnvelope.from({
|
|
3131
|
+
config,
|
|
3132
|
+
signatures: Array.from(
|
|
3133
|
+
{ length: MultisigConfig.maxSignatures },
|
|
3134
|
+
() => approval,
|
|
3135
|
+
),
|
|
2949
3136
|
})
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
expect(
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
3137
|
+
const serialized = SignatureEnvelope.serialize(envelope)
|
|
3138
|
+
|
|
3139
|
+
expect({
|
|
3140
|
+
hash: Hash.keccak256(serialized),
|
|
3141
|
+
length: Hex.size(serialized),
|
|
3142
|
+
signatureCount: envelope.signatures.length,
|
|
3143
|
+
}).toMatchInlineSnapshot(`
|
|
3144
|
+
{
|
|
3145
|
+
"hash": "0x1cb47fcf31fb1ebb3177ec1744eeda195e4d789920d1214af32baaa6150ad5e2",
|
|
3146
|
+
"length": 787,
|
|
3147
|
+
"signatureCount": 8,
|
|
3148
|
+
}
|
|
3149
|
+
`)
|
|
2956
3150
|
})
|
|
2957
3151
|
|
|
2958
|
-
test('
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
3152
|
+
test('behavior: accepts initial and current nested witnesses', () => {
|
|
3153
|
+
const initialEnvelope = SignatureEnvelope.deserialize(initial)
|
|
3154
|
+
const currentEnvelope = SignatureEnvelope.deserialize(current)
|
|
3155
|
+
if (
|
|
3156
|
+
initialEnvelope.type !== 'multisig' ||
|
|
3157
|
+
currentEnvelope.type !== 'multisig'
|
|
3158
|
+
)
|
|
3159
|
+
throw new Error('unreachable')
|
|
3160
|
+
|
|
3161
|
+
for (const signature of [initialEnvelope, currentEnvelope]) {
|
|
3162
|
+
const config = MultisigConfig.from({
|
|
3163
|
+
owners: [{ owner: signature.account, weight: 1 }],
|
|
3164
|
+
salt: ('0x' + 'ff'.repeat(32)) as Hex.Hex,
|
|
3165
|
+
threshold: 1,
|
|
3166
|
+
version: 1n,
|
|
3167
|
+
})
|
|
3168
|
+
const envelope = SignatureEnvelope.from({
|
|
3169
|
+
account: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
3170
|
+
config,
|
|
3171
|
+
signatures: [signature],
|
|
3172
|
+
})
|
|
3173
|
+
|
|
3174
|
+
expect(
|
|
3175
|
+
SignatureEnvelope.deserialize(SignatureEnvelope.serialize(envelope)),
|
|
3176
|
+
).toStrictEqual(envelope)
|
|
3177
|
+
}
|
|
2962
3178
|
})
|
|
2963
3179
|
|
|
2964
|
-
test('
|
|
3180
|
+
test('error: rejects an initial account mismatch', () => {
|
|
3181
|
+
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3182
|
+
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3183
|
+
|
|
2965
3184
|
expect(() =>
|
|
2966
3185
|
SignatureEnvelope.assert({
|
|
2967
|
-
|
|
2968
|
-
account,
|
|
2969
|
-
signatures: [],
|
|
3186
|
+
...envelope,
|
|
3187
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
2970
3188
|
}),
|
|
2971
3189
|
).toThrowErrorMatchingInlineSnapshot(
|
|
2972
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig
|
|
3190
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: initial multisig config does not derive account.]`,
|
|
2973
3191
|
)
|
|
2974
3192
|
})
|
|
2975
3193
|
|
|
2976
|
-
test('
|
|
3194
|
+
test('error: rejects a self-owned current witness', () => {
|
|
3195
|
+
const envelope = SignatureEnvelope.deserialize(current)
|
|
3196
|
+
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3197
|
+
const account = envelope.config.owners[0]!.owner
|
|
3198
|
+
|
|
2977
3199
|
expect(() =>
|
|
2978
|
-
SignatureEnvelope.assert({
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
)
|
|
3200
|
+
SignatureEnvelope.assert({ ...envelope, account }),
|
|
3201
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
3202
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig account cannot be an owner.]`,
|
|
3203
|
+
)
|
|
3204
|
+
})
|
|
3205
|
+
|
|
3206
|
+
test('error: rejects empty and excess approvals', () => {
|
|
3207
|
+
const envelope = SignatureEnvelope.deserialize(current)
|
|
3208
|
+
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
2987
3209
|
|
|
3210
|
+
expect(() =>
|
|
3211
|
+
SignatureEnvelope.assert({ ...envelope, signatures: [] }),
|
|
3212
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
3213
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig signatures cannot be empty.]`,
|
|
3214
|
+
)
|
|
2988
3215
|
expect(() =>
|
|
2989
3216
|
SignatureEnvelope.assert({
|
|
2990
|
-
|
|
2991
|
-
account,
|
|
3217
|
+
...envelope,
|
|
2992
3218
|
signatures: Array.from(
|
|
2993
3219
|
{ length: MultisigConfig.maxSignatures + 1 },
|
|
2994
|
-
() =>
|
|
3220
|
+
() => envelope.signatures[0]!,
|
|
2995
3221
|
),
|
|
2996
3222
|
}),
|
|
2997
3223
|
).toThrowErrorMatchingInlineSnapshot(
|
|
@@ -2999,7 +3225,23 @@ describe('multisig', () => {
|
|
|
2999
3225
|
)
|
|
3000
3226
|
})
|
|
3001
3227
|
|
|
3002
|
-
test('
|
|
3228
|
+
test('error: rejects keychain owner approvals', () => {
|
|
3229
|
+
const envelope = SignatureEnvelope.deserialize(current)
|
|
3230
|
+
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3231
|
+
|
|
3232
|
+
expect(() =>
|
|
3233
|
+
SignatureEnvelope.assert({
|
|
3234
|
+
...envelope,
|
|
3235
|
+
signatures: [signature_keychain_secp256k1],
|
|
3236
|
+
} as never),
|
|
3237
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
3238
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: keychain owner approvals are not allowed.]`,
|
|
3239
|
+
)
|
|
3240
|
+
})
|
|
3241
|
+
|
|
3242
|
+
test('error: rejects oversized primitive approvals', () => {
|
|
3243
|
+
const envelope = SignatureEnvelope.deserialize(current)
|
|
3244
|
+
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3003
3245
|
const oversized = {
|
|
3004
3246
|
...signature_webauthn,
|
|
3005
3247
|
metadata: {
|
|
@@ -3007,334 +3249,135 @@ describe('multisig', () => {
|
|
|
3007
3249
|
clientDataJSON: JSON.stringify({ value: 'x'.repeat(2048) }),
|
|
3008
3250
|
},
|
|
3009
3251
|
}
|
|
3252
|
+
|
|
3010
3253
|
expect(() =>
|
|
3011
3254
|
SignatureEnvelope.assert({
|
|
3012
|
-
|
|
3013
|
-
account,
|
|
3255
|
+
...envelope,
|
|
3014
3256
|
signatures: [oversized],
|
|
3015
3257
|
}),
|
|
3016
3258
|
).toThrowErrorMatchingInlineSnapshot(
|
|
3017
3259
|
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig owner signature exceeds 2049 bytes.]`,
|
|
3018
3260
|
)
|
|
3019
|
-
|
|
3020
|
-
const serialized = Hex.concat(
|
|
3021
|
-
'0x05',
|
|
3022
|
-
Rlp.fromHex([account, [SignatureEnvelope.serialize(oversized)]]),
|
|
3023
|
-
)
|
|
3024
|
-
expect(() => SignatureEnvelope.deserialize(serialized)).toThrowError(
|
|
3025
|
-
SignatureEnvelope.InvalidSerializedError,
|
|
3026
|
-
)
|
|
3027
3261
|
})
|
|
3028
3262
|
|
|
3029
|
-
test('
|
|
3263
|
+
test('error: rejects nesting deeper than two nodes', () => {
|
|
3264
|
+
const envelope = SignatureEnvelope.deserialize(initial)
|
|
3265
|
+
if (envelope.type !== 'multisig') throw new Error('unreachable')
|
|
3266
|
+
const depth2 = SignatureEnvelope.from({
|
|
3267
|
+
...envelope,
|
|
3268
|
+
signatures: [envelope],
|
|
3269
|
+
})
|
|
3270
|
+
|
|
3030
3271
|
expect(() =>
|
|
3031
3272
|
SignatureEnvelope.assert({
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
} as never),
|
|
3273
|
+
...envelope,
|
|
3274
|
+
signatures: [depth2],
|
|
3275
|
+
}),
|
|
3036
3276
|
).toThrowErrorMatchingInlineSnapshot(
|
|
3037
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval:
|
|
3277
|
+
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 2.]`,
|
|
3038
3278
|
)
|
|
3039
3279
|
})
|
|
3040
3280
|
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
test('assert: accepts the maximum nesting depth', () => {
|
|
3060
|
-
const depth2 = SignatureEnvelope.from({
|
|
3061
|
-
type: 'multisig',
|
|
3062
|
-
account,
|
|
3063
|
-
signatures: [nested],
|
|
3064
|
-
})
|
|
3065
|
-
expect(() => SignatureEnvelope.assert(depth2)).not.toThrowError()
|
|
3066
|
-
})
|
|
3067
|
-
|
|
3068
|
-
test('accepts a nested approval above the primitive byte limit', () => {
|
|
3069
|
-
const primitive = SignatureEnvelope.from({
|
|
3070
|
-
...signature_webauthn,
|
|
3071
|
-
metadata: {
|
|
3072
|
-
...signature_webauthn.metadata,
|
|
3073
|
-
clientDataJSON: JSON.stringify({ value: 'x'.repeat(1_050) }),
|
|
3074
|
-
},
|
|
3075
|
-
signature: {
|
|
3076
|
-
r: signature_webauthn.signature.r,
|
|
3077
|
-
s: signature_webauthn.signature.s,
|
|
3078
|
-
},
|
|
3079
|
-
})
|
|
3080
|
-
const largeNested = SignatureEnvelope.from({
|
|
3081
|
-
type: 'multisig',
|
|
3082
|
-
account: nestedAccount,
|
|
3083
|
-
signatures: [primitive, primitive],
|
|
3084
|
-
})
|
|
3085
|
-
expect(Hex.size(SignatureEnvelope.serialize(primitive))).toBeLessThan(
|
|
3086
|
-
MultisigConfig.maxOwnerSignatureBytes,
|
|
3087
|
-
)
|
|
3088
|
-
expect(
|
|
3089
|
-
Hex.size(SignatureEnvelope.serialize(largeNested)),
|
|
3090
|
-
).toBeGreaterThan(MultisigConfig.maxOwnerSignatureBytes)
|
|
3091
|
-
|
|
3092
|
-
const parent = SignatureEnvelope.from({
|
|
3093
|
-
type: 'multisig',
|
|
3094
|
-
account,
|
|
3095
|
-
signatures: [largeNested],
|
|
3096
|
-
})
|
|
3097
|
-
expect(
|
|
3098
|
-
SignatureEnvelope.deserialize(SignatureEnvelope.serialize(parent)),
|
|
3099
|
-
).toEqual(parent)
|
|
3100
|
-
})
|
|
3101
|
-
|
|
3102
|
-
test('assert: rejects nesting deeper than the maximum', () => {
|
|
3103
|
-
const depth3 = SignatureEnvelope.from({
|
|
3104
|
-
type: 'multisig',
|
|
3105
|
-
account,
|
|
3106
|
-
signatures: [
|
|
3107
|
-
SignatureEnvelope.from({
|
|
3108
|
-
type: 'multisig',
|
|
3109
|
-
account: nestedAccount,
|
|
3110
|
-
signatures: [nested],
|
|
3111
|
-
}),
|
|
3112
|
-
],
|
|
3113
|
-
})
|
|
3114
|
-
expect(() =>
|
|
3115
|
-
SignatureEnvelope.assert(depth3),
|
|
3116
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3117
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 2.]`,
|
|
3118
|
-
)
|
|
3119
|
-
})
|
|
3120
|
-
|
|
3121
|
-
test('deserialize: rejects nesting deeper than the maximum during decode', () => {
|
|
3122
|
-
const primitive = SignatureEnvelope.serialize(innerP256)
|
|
3123
|
-
const depth1 = Hex.concat(
|
|
3124
|
-
'0x05',
|
|
3125
|
-
Rlp.fromHex([nestedAccount, [primitive]]),
|
|
3126
|
-
)
|
|
3127
|
-
const depth2 = Hex.concat('0x05', Rlp.fromHex([nestedAccount, [depth1]]))
|
|
3128
|
-
const depth3 = Hex.concat('0x05', Rlp.fromHex([account, [depth2]]))
|
|
3129
|
-
|
|
3130
|
-
expect(() => SignatureEnvelope.deserialize(depth3)).toThrowError(
|
|
3131
|
-
SignatureEnvelope.InvalidSerializedError,
|
|
3132
|
-
)
|
|
3133
|
-
})
|
|
3134
|
-
|
|
3135
|
-
test('assert: rejects nested approvals carrying `init`', () => {
|
|
3136
|
-
expect(() =>
|
|
3137
|
-
SignatureEnvelope.assert({
|
|
3138
|
-
type: 'multisig',
|
|
3139
|
-
account,
|
|
3140
|
-
signatures: [
|
|
3141
|
-
{
|
|
3142
|
-
...nested,
|
|
3143
|
-
init: {
|
|
3144
|
-
threshold: 1,
|
|
3145
|
-
owners: [{ owner: nestedAccount, weight: 1 }],
|
|
3281
|
+
test.each([
|
|
3282
|
+
{
|
|
3283
|
+
name: 'two fields',
|
|
3284
|
+
value: Rlp.fromHex([
|
|
3285
|
+
'0x9dba7f426b711d4893c11611eacf7cc334e7146b',
|
|
3286
|
+
[Hex.slice(initial, -65)],
|
|
3287
|
+
]),
|
|
3288
|
+
},
|
|
3289
|
+
{
|
|
3290
|
+
name: 'trailing field',
|
|
3291
|
+
value: Rlp.fromHex([
|
|
3292
|
+
'0x9dba7f426b711d4893c11611eacf7cc334e7146b',
|
|
3293
|
+
MultisigConfig.toTuple(
|
|
3294
|
+
MultisigConfig.from({
|
|
3295
|
+
owners: [
|
|
3296
|
+
{
|
|
3297
|
+
owner: '0x7e5f4552091a69125d5dfcb7b8c2659029395bdf',
|
|
3298
|
+
weight: 1,
|
|
3146
3299
|
},
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3151
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: nested multisig owner approvals cannot carry \`init\`.]`,
|
|
3152
|
-
)
|
|
3153
|
-
})
|
|
3154
|
-
})
|
|
3155
|
-
|
|
3156
|
-
describe('init (bootstrap)', () => {
|
|
3157
|
-
const init = {
|
|
3158
|
-
salt: `0x${'00'.repeat(32)}` as const,
|
|
3159
|
-
threshold: 1,
|
|
3160
|
-
owners: [
|
|
3161
|
-
{
|
|
3162
|
-
owner: '0x1111111111111111111111111111111111111111' as const,
|
|
3163
|
-
weight: 1,
|
|
3164
|
-
},
|
|
3165
|
-
],
|
|
3166
|
-
}
|
|
3167
|
-
const bootstrapAccount = MultisigConfig.getAddress(init)
|
|
3168
|
-
|
|
3169
|
-
const bootstrapEnvelope = SignatureEnvelope.from({
|
|
3170
|
-
type: 'multisig',
|
|
3171
|
-
account: bootstrapAccount,
|
|
3172
|
-
signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
|
|
3173
|
-
init,
|
|
3174
|
-
})
|
|
3175
|
-
|
|
3176
|
-
test('serialize/deserialize round-trip with init', () => {
|
|
3177
|
-
const serialized = SignatureEnvelope.serialize(bootstrapEnvelope)
|
|
3178
|
-
expect(SignatureEnvelope.deserialize(serialized)).toEqual(
|
|
3179
|
-
bootstrapEnvelope,
|
|
3180
|
-
)
|
|
3181
|
-
})
|
|
3182
|
-
|
|
3183
|
-
test('deserialize: validates bootstrap init semantics', () => {
|
|
3184
|
-
const invalid = Hex.concat(
|
|
3185
|
-
'0x05',
|
|
3186
|
-
Rlp.fromHex([
|
|
3187
|
-
[MultisigConfig.zeroSalt, '0x01', []],
|
|
3188
|
-
[SignatureEnvelope.serialize(innerP256)],
|
|
3189
|
-
]),
|
|
3190
|
-
)
|
|
3191
|
-
expect(() => SignatureEnvelope.deserialize(invalid)).toThrowError(
|
|
3192
|
-
MultisigConfig.InvalidConfigError,
|
|
3193
|
-
)
|
|
3194
|
-
})
|
|
3195
|
-
|
|
3196
|
-
test('deserialize: rejects noncanonical bootstrap field widths', () => {
|
|
3197
|
-
const signatures = [SignatureEnvelope.serialize(innerP256)]
|
|
3198
|
-
const shortSalt = Hex.concat(
|
|
3199
|
-
'0x05',
|
|
3200
|
-
Rlp.fromHex([
|
|
3201
|
-
[`0x${'00'.repeat(31)}`, '0x01', [[init.owners[0]!.owner, '0x01']]],
|
|
3202
|
-
signatures,
|
|
3203
|
-
]),
|
|
3204
|
-
)
|
|
3205
|
-
const wideThreshold = Hex.concat(
|
|
3206
|
-
'0x05',
|
|
3207
|
-
Rlp.fromHex([
|
|
3208
|
-
[init.salt, '0x0001', [[init.owners[0]!.owner, '0x01']]],
|
|
3209
|
-
signatures,
|
|
3210
|
-
]),
|
|
3211
|
-
)
|
|
3212
|
-
const wideWeight = Hex.concat(
|
|
3213
|
-
'0x05',
|
|
3214
|
-
Rlp.fromHex([
|
|
3215
|
-
[init.salt, '0x01', [[init.owners[0]!.owner, '0x0001']]],
|
|
3216
|
-
signatures,
|
|
3217
|
-
]),
|
|
3218
|
-
)
|
|
3219
|
-
|
|
3220
|
-
for (const serialized of [shortSalt, wideThreshold, wideWeight])
|
|
3221
|
-
expect(() => SignatureEnvelope.deserialize(serialized)).toThrowError(
|
|
3222
|
-
SignatureEnvelope.InvalidSerializedError,
|
|
3223
|
-
)
|
|
3224
|
-
})
|
|
3225
|
-
|
|
3226
|
-
test('serialize: wire shape is `0x05 || rlp([init, signatures])`', () => {
|
|
3227
|
-
const serialized = SignatureEnvelope.serialize(bootstrapEnvelope)
|
|
3228
|
-
const [address] = Rlp.toHex(Hex.slice(serialized, 1)) as readonly [
|
|
3229
|
-
MultisigConfig.Tuple,
|
|
3230
|
-
readonly Hex.Hex[],
|
|
3231
|
-
]
|
|
3232
|
-
expect(MultisigConfig.fromTuple(address)).toEqual(init)
|
|
3233
|
-
})
|
|
3234
|
-
|
|
3235
|
-
test('serialize/deserialize round-trip preserves non-zero salt', () => {
|
|
3236
|
-
const saltedInit = {
|
|
3237
|
-
...init,
|
|
3238
|
-
salt: `0x${'42'.repeat(32)}` as const,
|
|
3239
|
-
}
|
|
3240
|
-
const salted = SignatureEnvelope.from({
|
|
3241
|
-
type: 'multisig',
|
|
3242
|
-
account: MultisigConfig.getAddress(saltedInit),
|
|
3243
|
-
signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
|
|
3244
|
-
init: saltedInit,
|
|
3245
|
-
})
|
|
3246
|
-
const serialized = SignatureEnvelope.serialize(salted)
|
|
3247
|
-
const deserialized = SignatureEnvelope.deserialize(
|
|
3248
|
-
serialized,
|
|
3249
|
-
) as SignatureEnvelope.Multisig
|
|
3250
|
-
expect(deserialized.init?.salt).toBe(`0x${'42'.repeat(32)}`)
|
|
3251
|
-
expect(deserialized).toEqual(salted)
|
|
3252
|
-
})
|
|
3253
|
-
|
|
3254
|
-
test('absent init has no `init` key after deserialize', () => {
|
|
3255
|
-
const serialized = SignatureEnvelope.serialize(envelope)
|
|
3256
|
-
const deserialized = SignatureEnvelope.deserialize(serialized)
|
|
3257
|
-
expect('init' in deserialized).toBe(false)
|
|
3258
|
-
})
|
|
3259
|
-
|
|
3260
|
-
test('init absent vs present produce different serializations', () => {
|
|
3261
|
-
expect(SignatureEnvelope.serialize(envelope)).not.toBe(
|
|
3262
|
-
SignatureEnvelope.serialize(bootstrapEnvelope),
|
|
3263
|
-
)
|
|
3264
|
-
})
|
|
3265
|
-
|
|
3266
|
-
test('toRpc/fromRpc round-trip with init', () => {
|
|
3267
|
-
const rpc = SignatureEnvelope.toRpc(
|
|
3268
|
-
bootstrapEnvelope,
|
|
3269
|
-
) as SignatureEnvelope.MultisigRpc
|
|
3270
|
-
expect(rpc.init).toEqual(init)
|
|
3271
|
-
expect(rpc.account).toBeUndefined()
|
|
3272
|
-
expect('type' in rpc).toBe(false)
|
|
3273
|
-
expect(SignatureEnvelope.fromRpc(rpc)).toEqual(bootstrapEnvelope)
|
|
3274
|
-
})
|
|
3275
|
-
|
|
3276
|
-
test('toRpc encodes owner approvals as structured RPC envelopes', () => {
|
|
3277
|
-
const multisig = bootstrapEnvelope as SignatureEnvelope.Multisig
|
|
3278
|
-
const rpc = SignatureEnvelope.toRpc(
|
|
3279
|
-
multisig,
|
|
3280
|
-
) as SignatureEnvelope.MultisigRpc
|
|
3281
|
-
expect(rpc.signatures).toEqual(
|
|
3282
|
-
multisig.signatures.map((signature) =>
|
|
3283
|
-
SignatureEnvelope.toRpc(signature),
|
|
3300
|
+
],
|
|
3301
|
+
threshold: 1,
|
|
3302
|
+
}),
|
|
3284
3303
|
),
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3304
|
+
[Hex.slice(initial, -65)],
|
|
3305
|
+
'0x',
|
|
3306
|
+
]),
|
|
3307
|
+
},
|
|
3308
|
+
])('error: rejects malformed $name wire shape', ({ value }) => {
|
|
3309
|
+
expect(() =>
|
|
3310
|
+
SignatureEnvelope.deserialize(Hex.concat('0x05', value)),
|
|
3311
|
+
).toThrowError(SignatureEnvelope.InvalidSerializedError)
|
|
3312
|
+
})
|
|
3313
|
+
|
|
3314
|
+
test.each([
|
|
3315
|
+
{
|
|
3316
|
+
field: 'salt',
|
|
3317
|
+
value: [
|
|
3318
|
+
('0x' + '00'.repeat(31)) as Hex.Hex,
|
|
3319
|
+
'0x',
|
|
3320
|
+
'0x01',
|
|
3321
|
+
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3322
|
+
],
|
|
3323
|
+
},
|
|
3324
|
+
{
|
|
3325
|
+
field: 'oversized version',
|
|
3326
|
+
value: [
|
|
3327
|
+
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3328
|
+
('0x' + '01'.repeat(9)) as Hex.Hex,
|
|
3329
|
+
'0x01',
|
|
3330
|
+
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3331
|
+
],
|
|
3332
|
+
},
|
|
3333
|
+
{
|
|
3334
|
+
field: 'zero version',
|
|
3335
|
+
value: [
|
|
3336
|
+
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3337
|
+
'0x00',
|
|
3338
|
+
'0x01',
|
|
3339
|
+
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3340
|
+
],
|
|
3341
|
+
},
|
|
3342
|
+
{
|
|
3343
|
+
field: 'zero-prefixed version',
|
|
3344
|
+
value: [
|
|
3345
|
+
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3346
|
+
'0x0001',
|
|
3347
|
+
'0x01',
|
|
3348
|
+
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3349
|
+
],
|
|
3350
|
+
},
|
|
3351
|
+
{
|
|
3352
|
+
field: 'threshold',
|
|
3353
|
+
value: [
|
|
3354
|
+
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3355
|
+
'0x',
|
|
3356
|
+
'0x0001',
|
|
3357
|
+
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x01']],
|
|
3358
|
+
],
|
|
3359
|
+
},
|
|
3360
|
+
{
|
|
3361
|
+
field: 'weight',
|
|
3362
|
+
value: [
|
|
3363
|
+
('0x' + '00'.repeat(32)) as Hex.Hex,
|
|
3364
|
+
'0x',
|
|
3365
|
+
'0x01',
|
|
3366
|
+
[['0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', '0x0001']],
|
|
3367
|
+
],
|
|
3368
|
+
},
|
|
3369
|
+
])('error: rejects a noncanonical $field', ({ value }) => {
|
|
3370
|
+
const malformed = Hex.concat(
|
|
3371
|
+
'0x05',
|
|
3372
|
+
Rlp.fromHex([
|
|
3373
|
+
'0x9dba7f426b711d4893c11611eacf7cc334e7146b',
|
|
3374
|
+
value as never,
|
|
3375
|
+
[Hex.slice(initial, -65)],
|
|
3376
|
+
]),
|
|
3377
|
+
)
|
|
3326
3378
|
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
type: 'multisig',
|
|
3331
|
-
account,
|
|
3332
|
-
signatures: [innerP256],
|
|
3333
|
-
init,
|
|
3334
|
-
}),
|
|
3335
|
-
).toThrowErrorMatchingInlineSnapshot(
|
|
3336
|
-
`[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig init does not derive account.]`,
|
|
3337
|
-
)
|
|
3338
|
-
})
|
|
3379
|
+
expect(() => SignatureEnvelope.deserialize(malformed)).toThrowError(
|
|
3380
|
+
SignatureEnvelope.InvalidSerializedError,
|
|
3381
|
+
)
|
|
3339
3382
|
})
|
|
3340
3383
|
})
|