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.
- package/CHANGELOG.md +24 -0
- package/_cjs/tempo/MultisigConfig.js +19 -21
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +62 -28
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/MultisigConfig.js +60 -49
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +118 -41
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/index.js +4 -4
- package/_esm/version.js +1 -1
- package/_types/tempo/MultisigConfig.d.ts +68 -56
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +90 -26
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +4 -4
- package/_types/version.d.ts +1 -1
- package/package.json +1 -1
- package/tempo/MultisigConfig.test.ts +62 -36
- package/tempo/MultisigConfig.ts +102 -95
- package/tempo/SignatureEnvelope.test.ts +226 -27
- package/tempo/SignatureEnvelope.ts +174 -69
- package/tempo/e2e.test.ts +263 -48
- package/tempo/index.ts +4 -4
- package/version.ts +1 -1
|
@@ -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
|
-
|
|
1702
|
-
|
|
1703
|
-
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
|
-
|
|
1716
|
-
Secp256k1.sign({ payload: digest, privateKey }),
|
|
1717
|
-
)
|
|
1718
|
-
return { address, signature } as const
|
|
1779
|
+
return { address, privateKey } as const
|
|
1719
1780
|
})
|
|
1720
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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),
|