ox 0.14.28 → 0.14.29
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 +6 -0
- package/_cjs/tempo/MultisigConfig.js +11 -3
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +39 -18
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/MultisigConfig.js +62 -11
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +93 -26
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/index.js +2 -2
- package/_esm/version.js +1 -1
- package/_types/tempo/MultisigConfig.d.ts +72 -16
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +77 -15
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +2 -2
- package/_types/version.d.ts +1 -1
- package/package.json +1 -1
- package/tempo/MultisigConfig.test.ts +21 -12
- package/tempo/MultisigConfig.ts +96 -18
- package/tempo/SignatureEnvelope.test.ts +124 -26
- package/tempo/SignatureEnvelope.ts +144 -44
- package/tempo/e2e.test.ts +20 -36
- package/tempo/index.ts +2 -2
- package/version.ts +1 -1
|
@@ -1097,6 +1097,80 @@ describe('from', () => {
|
|
|
1097
1097
|
expect(envelope.keyId).toBe(explicitKeyId)
|
|
1098
1098
|
})
|
|
1099
1099
|
})
|
|
1100
|
+
|
|
1101
|
+
describe('multisig', () => {
|
|
1102
|
+
const genesisConfig = MultisigConfig.from({
|
|
1103
|
+
threshold: 1,
|
|
1104
|
+
owners: [
|
|
1105
|
+
{
|
|
1106
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
1107
|
+
weight: 1,
|
|
1108
|
+
},
|
|
1109
|
+
],
|
|
1110
|
+
})
|
|
1111
|
+
|
|
1112
|
+
test('behavior: derives `account`/`genesisConfigId` from `genesisConfig`', () => {
|
|
1113
|
+
const envelope = SignatureEnvelope.from({
|
|
1114
|
+
genesisConfig,
|
|
1115
|
+
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1116
|
+
})
|
|
1117
|
+
|
|
1118
|
+
expect(envelope).toMatchObject({
|
|
1119
|
+
type: 'multisig',
|
|
1120
|
+
account: MultisigConfig.getAddress(genesisConfig),
|
|
1121
|
+
genesisConfigId: MultisigConfig.toId(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, genesisConfigId}` form still works', () => {
|
|
1159
|
+
const account = MultisigConfig.getAddress(genesisConfig)
|
|
1160
|
+
const genesisConfigId = MultisigConfig.toId(genesisConfig)
|
|
1161
|
+
const envelope = SignatureEnvelope.from({
|
|
1162
|
+
account,
|
|
1163
|
+
genesisConfigId,
|
|
1164
|
+
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
1165
|
+
})
|
|
1166
|
+
|
|
1167
|
+
expect(envelope).toMatchObject({
|
|
1168
|
+
type: 'multisig',
|
|
1169
|
+
account,
|
|
1170
|
+
genesisConfigId,
|
|
1171
|
+
})
|
|
1172
|
+
})
|
|
1173
|
+
})
|
|
1100
1174
|
})
|
|
1101
1175
|
|
|
1102
1176
|
describe('getType', () => {
|
|
@@ -1698,33 +1772,40 @@ describe('serialize', () => {
|
|
|
1698
1772
|
})
|
|
1699
1773
|
|
|
1700
1774
|
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 }, () => {
|
|
1775
|
+
// Build owner key pairs first so we can construct a real genesis config
|
|
1776
|
+
// whose owner set matches the keys that produce the approvals below.
|
|
1777
|
+
const ownerKeys = Array.from({ length: 3 }, () => {
|
|
1711
1778
|
const privateKey = Secp256k1.randomPrivateKey()
|
|
1712
1779
|
const address = Address.fromPublicKey(
|
|
1713
1780
|
Secp256k1.getPublicKey({ privateKey }),
|
|
1714
1781
|
)
|
|
1715
|
-
|
|
1716
|
-
Secp256k1.sign({ payload: digest, privateKey }),
|
|
1717
|
-
)
|
|
1718
|
-
return { address, signature } as const
|
|
1782
|
+
return { address, privateKey } as const
|
|
1719
1783
|
})
|
|
1720
|
-
const
|
|
1784
|
+
const ascendingOwners = [...ownerKeys].sort((a, b) =>
|
|
1721
1785
|
Hex.toBigInt(a.address) < Hex.toBigInt(b.address) ? -1 : 1,
|
|
1722
1786
|
)
|
|
1723
1787
|
|
|
1788
|
+
const genesisConfig = MultisigConfig.from({
|
|
1789
|
+
threshold: 2,
|
|
1790
|
+
owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
|
|
1791
|
+
})
|
|
1792
|
+
const payload = `0x${'42'.repeat(32)}` as const
|
|
1793
|
+
const digest = MultisigConfig.getSignPayload({ payload, genesisConfig })
|
|
1794
|
+
|
|
1795
|
+
const owners = ownerKeys.map((owner) => ({
|
|
1796
|
+
address: owner.address,
|
|
1797
|
+
signature: SignatureEnvelope.from(
|
|
1798
|
+
Secp256k1.sign({ payload: digest, privateKey: owner.privateKey }),
|
|
1799
|
+
),
|
|
1800
|
+
}))
|
|
1801
|
+
const ascending = ascendingOwners.map((o) => ({
|
|
1802
|
+
address: o.address,
|
|
1803
|
+
signature: owners.find((x) => x.address === o.address)!.signature,
|
|
1804
|
+
}))
|
|
1805
|
+
|
|
1724
1806
|
test('behavior: orders approvals ascending by recovered owner address', () => {
|
|
1725
1807
|
const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1726
|
-
|
|
1727
|
-
configId,
|
|
1808
|
+
genesisConfig,
|
|
1728
1809
|
payload,
|
|
1729
1810
|
// Provide approvals in reverse of the canonical order.
|
|
1730
1811
|
signatures: [...ascending].reverse().map((owner) => owner.signature),
|
|
@@ -1736,8 +1817,7 @@ describe('sortMultisigApprovals', () => {
|
|
|
1736
1817
|
const signatures = ascending.map((owner) => owner.signature)
|
|
1737
1818
|
expect(
|
|
1738
1819
|
SignatureEnvelope.sortMultisigApprovals({
|
|
1739
|
-
|
|
1740
|
-
configId,
|
|
1820
|
+
genesisConfig,
|
|
1741
1821
|
payload,
|
|
1742
1822
|
signatures,
|
|
1743
1823
|
}),
|
|
@@ -1746,8 +1826,7 @@ describe('sortMultisigApprovals', () => {
|
|
|
1746
1826
|
|
|
1747
1827
|
test('behavior: recovered order matches the config owner order', () => {
|
|
1748
1828
|
const ordered = SignatureEnvelope.sortMultisigApprovals({
|
|
1749
|
-
|
|
1750
|
-
configId,
|
|
1829
|
+
genesisConfig,
|
|
1751
1830
|
payload,
|
|
1752
1831
|
signatures: owners.map((owner) => owner.signature),
|
|
1753
1832
|
})
|
|
@@ -1756,6 +1835,25 @@ describe('sortMultisigApprovals', () => {
|
|
|
1756
1835
|
)
|
|
1757
1836
|
expect(recovered).toEqual(ascending.map((owner) => owner.address))
|
|
1758
1837
|
})
|
|
1838
|
+
|
|
1839
|
+
test('behavior: `genesisConfig` and `{account, genesisConfigId}` produce identical ordering', () => {
|
|
1840
|
+
const account = MultisigConfig.getAddress(genesisConfig)
|
|
1841
|
+
const genesisConfigId = MultisigConfig.toId(genesisConfig)
|
|
1842
|
+
const signatures = owners.map((owner) => owner.signature)
|
|
1843
|
+
|
|
1844
|
+
const fromConfig = SignatureEnvelope.sortMultisigApprovals({
|
|
1845
|
+
genesisConfig,
|
|
1846
|
+
payload,
|
|
1847
|
+
signatures,
|
|
1848
|
+
})
|
|
1849
|
+
const fromIds = SignatureEnvelope.sortMultisigApprovals({
|
|
1850
|
+
account,
|
|
1851
|
+
genesisConfigId,
|
|
1852
|
+
payload,
|
|
1853
|
+
signatures,
|
|
1854
|
+
})
|
|
1855
|
+
expect(fromConfig).toEqual(fromIds)
|
|
1856
|
+
})
|
|
1759
1857
|
})
|
|
1760
1858
|
|
|
1761
1859
|
describe('validate', () => {
|
|
@@ -2757,7 +2855,7 @@ describe('CoercionError', () => {
|
|
|
2757
2855
|
|
|
2758
2856
|
describe('multisig', () => {
|
|
2759
2857
|
const account = '0x8ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7ae'
|
|
2760
|
-
const
|
|
2858
|
+
const genesisConfigId =
|
|
2761
2859
|
'0x01781fe551182476f2422c759e82d81c92e3263737afbbad57def6e8b69d21f5'
|
|
2762
2860
|
|
|
2763
2861
|
// P256 signatures do not carry `yParity` in the wire format, so use a clean
|
|
@@ -2771,7 +2869,7 @@ describe('multisig', () => {
|
|
|
2771
2869
|
const envelope = SignatureEnvelope.from({
|
|
2772
2870
|
type: 'multisig',
|
|
2773
2871
|
account,
|
|
2774
|
-
|
|
2872
|
+
genesisConfigId,
|
|
2775
2873
|
signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
|
|
2776
2874
|
})
|
|
2777
2875
|
|
|
@@ -2825,7 +2923,7 @@ describe('multisig', () => {
|
|
|
2825
2923
|
const bootstrapEnvelope = SignatureEnvelope.from({
|
|
2826
2924
|
type: 'multisig',
|
|
2827
2925
|
account,
|
|
2828
|
-
|
|
2926
|
+
genesisConfigId,
|
|
2829
2927
|
signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
|
|
2830
2928
|
init,
|
|
2831
2929
|
})
|
|
@@ -2841,7 +2939,7 @@ describe('multisig', () => {
|
|
|
2841
2939
|
const salted = SignatureEnvelope.from({
|
|
2842
2940
|
type: 'multisig',
|
|
2843
2941
|
account,
|
|
2844
|
-
|
|
2942
|
+
genesisConfigId,
|
|
2845
2943
|
signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
|
|
2846
2944
|
init: { ...init, salt: `0x${'42'.repeat(32)}` },
|
|
2847
2945
|
})
|
|
@@ -2895,7 +2993,7 @@ describe('multisig', () => {
|
|
|
2895
2993
|
SignatureEnvelope.assert({
|
|
2896
2994
|
type: 'multisig',
|
|
2897
2995
|
account,
|
|
2898
|
-
|
|
2996
|
+
genesisConfigId,
|
|
2899
2997
|
signatures: [],
|
|
2900
2998
|
init: { threshold: 1, owners: [] },
|
|
2901
2999
|
} as never),
|
|
@@ -74,7 +74,7 @@ export type GetType<
|
|
|
74
74
|
? 'keychain'
|
|
75
75
|
: envelope extends {
|
|
76
76
|
account: Address.Address
|
|
77
|
-
|
|
77
|
+
genesisConfigId: `0x${string}`
|
|
78
78
|
signatures: any
|
|
79
79
|
}
|
|
80
80
|
? 'multisig'
|
|
@@ -161,7 +161,7 @@ export type Multisig<bigintType = bigint, numberType = number> = {
|
|
|
161
161
|
/** Native multisig account address. */
|
|
162
162
|
account: Address.Address
|
|
163
163
|
/** Permanent config ID derived from the initial multisig config. */
|
|
164
|
-
|
|
164
|
+
genesisConfigId: Hex.Hex
|
|
165
165
|
/** Primitive owner approvals over the multisig owner approval digest. */
|
|
166
166
|
signatures: readonly SignatureEnvelope<bigintType, numberType>[]
|
|
167
167
|
/**
|
|
@@ -175,6 +175,11 @@ export type Multisig<bigintType = bigint, numberType = number> = {
|
|
|
175
175
|
export type MultisigRpc = {
|
|
176
176
|
type: 'multisig'
|
|
177
177
|
account: Address.Address
|
|
178
|
+
/**
|
|
179
|
+
* The permanent multisig config ID (TIP-1061 wire field `config_id`).
|
|
180
|
+
* Maps to `genesisConfigId` on the typed
|
|
181
|
+
* {@link ox#SignatureEnvelope.Multisig} envelope.
|
|
182
|
+
*/
|
|
178
183
|
configId: Hex.Hex
|
|
179
184
|
/**
|
|
180
185
|
* Encoded primitive owner approvals (raw serialized signatures), matching the
|
|
@@ -328,7 +333,7 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
|
|
|
328
333
|
const multisig = envelope as Multisig
|
|
329
334
|
const missing: string[] = []
|
|
330
335
|
if (!multisig.account) missing.push('account')
|
|
331
|
-
if (!multisig.
|
|
336
|
+
if (!multisig.genesisConfigId) missing.push('genesisConfigId')
|
|
332
337
|
if (!Array.isArray(multisig.signatures)) missing.push('signatures')
|
|
333
338
|
if (missing.length > 0)
|
|
334
339
|
throw new MissingPropertiesError({ envelope, missing, type: 'multisig' })
|
|
@@ -613,10 +618,10 @@ export function deserialize(value: Serialized): SignatureEnvelope {
|
|
|
613
618
|
}
|
|
614
619
|
|
|
615
620
|
if (typeId === serializedMultisigType) {
|
|
616
|
-
// Wire format: `0x05 || rlp([account,
|
|
621
|
+
// Wire format: `0x05 || rlp([account, genesisConfigId, signatures, init])`. `init`
|
|
617
622
|
// is optional: absent when the element is missing or the `0x80` placeholder
|
|
618
623
|
// (decoded as the empty string `0x`), otherwise the bootstrap config list.
|
|
619
|
-
const [account,
|
|
624
|
+
const [account, genesisConfigId, signatures, init] = Rlp.toHex(data) as [
|
|
620
625
|
Hex.Hex,
|
|
621
626
|
Hex.Hex,
|
|
622
627
|
readonly Hex.Hex[],
|
|
@@ -625,7 +630,7 @@ export function deserialize(value: Serialized): SignatureEnvelope {
|
|
|
625
630
|
return {
|
|
626
631
|
type: 'multisig',
|
|
627
632
|
account,
|
|
628
|
-
|
|
633
|
+
genesisConfigId,
|
|
629
634
|
signatures: signatures.map((signature) => deserialize(signature)),
|
|
630
635
|
...(init && init !== '0x'
|
|
631
636
|
? {
|
|
@@ -753,6 +758,43 @@ export function deserialize(value: Serialized): SignatureEnvelope {
|
|
|
753
758
|
* })
|
|
754
759
|
* ```
|
|
755
760
|
*
|
|
761
|
+
* @example
|
|
762
|
+
* ### Multisig (from genesis config)
|
|
763
|
+
*
|
|
764
|
+
* Pass `genesisConfig` to derive `account` and `genesisConfigId` automatically.
|
|
765
|
+
* Set `init: true` to opt into bootstrap (uses `genesisConfig` as the
|
|
766
|
+
* bootstrap `init`); omit `init` for subsequent (non-bootstrap) transactions.
|
|
767
|
+
*
|
|
768
|
+
* ```ts twoslash
|
|
769
|
+
* import { Secp256k1 } from 'ox'
|
|
770
|
+
* import { MultisigConfig, SignatureEnvelope } from 'ox/tempo'
|
|
771
|
+
*
|
|
772
|
+
* const genesisConfig = MultisigConfig.from({
|
|
773
|
+
* threshold: 1,
|
|
774
|
+
* owners: [
|
|
775
|
+
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
776
|
+
* ],
|
|
777
|
+
* })
|
|
778
|
+
*
|
|
779
|
+
* const privateKey = Secp256k1.randomPrivateKey()
|
|
780
|
+
* const signature = SignatureEnvelope.from(
|
|
781
|
+
* Secp256k1.sign({ payload: '0xdeadbeef', privateKey }),
|
|
782
|
+
* )
|
|
783
|
+
*
|
|
784
|
+
* // Bootstrap transaction
|
|
785
|
+
* const bootstrap = SignatureEnvelope.from({
|
|
786
|
+
* genesisConfig,
|
|
787
|
+
* signatures: [signature],
|
|
788
|
+
* init: true,
|
|
789
|
+
* })
|
|
790
|
+
*
|
|
791
|
+
* // Subsequent (non-bootstrap) transactions
|
|
792
|
+
* const subsequent = SignatureEnvelope.from({
|
|
793
|
+
* genesisConfig,
|
|
794
|
+
* signatures: [signature],
|
|
795
|
+
* })
|
|
796
|
+
* ```
|
|
797
|
+
*
|
|
756
798
|
* @param value - The value to coerce (either a hex string or signature envelope).
|
|
757
799
|
* @returns The signature envelope.
|
|
758
800
|
*/
|
|
@@ -774,20 +816,42 @@ export function from<const value extends from.Value>(
|
|
|
774
816
|
const type = getType(value)
|
|
775
817
|
|
|
776
818
|
if (type === 'multisig') {
|
|
777
|
-
const multisig = value as Multisig
|
|
819
|
+
const multisig = value as Multisig & {
|
|
820
|
+
genesisConfig?: MultisigConfig.Config | undefined
|
|
821
|
+
init?: MultisigConfig.Config | boolean | undefined
|
|
822
|
+
}
|
|
823
|
+
const { genesisConfig, init, ...rest } = multisig
|
|
824
|
+
// Derive `account`/`genesisConfigId` from `genesisConfig` when not provided
|
|
825
|
+
// explicitly.
|
|
826
|
+
const account = (() => {
|
|
827
|
+
if (rest.account) return rest.account
|
|
828
|
+
if (genesisConfig) return MultisigConfig.getAddress(genesisConfig)
|
|
829
|
+
return rest.account
|
|
830
|
+
})()
|
|
831
|
+
const genesisConfigId = (() => {
|
|
832
|
+
if (rest.genesisConfigId) return rest.genesisConfigId
|
|
833
|
+
if (genesisConfig) return MultisigConfig.toId(genesisConfig)
|
|
834
|
+
return rest.genesisConfigId
|
|
835
|
+
})()
|
|
836
|
+
// `init: true` opts into bootstrap using the supplied `genesisConfig`.
|
|
837
|
+
// Otherwise, `init` is treated as the explicit bootstrap config (or
|
|
838
|
+
// omitted).
|
|
839
|
+
const initSource = init === true ? genesisConfig : init || undefined
|
|
778
840
|
return {
|
|
779
|
-
...
|
|
780
|
-
|
|
841
|
+
...rest,
|
|
842
|
+
account,
|
|
843
|
+
genesisConfigId,
|
|
844
|
+
signatures: rest.signatures.map((signature) => from(signature)),
|
|
781
845
|
// Normalize the bootstrap config (sorts owners, defaults the salt) so the
|
|
782
846
|
// in-memory envelope matches what `deserialize` reconstructs.
|
|
783
|
-
...(
|
|
847
|
+
...(initSource ? { init: MultisigConfig.from(initSource) } : {}),
|
|
784
848
|
type,
|
|
785
849
|
} as never
|
|
786
850
|
}
|
|
787
851
|
|
|
788
852
|
return {
|
|
789
853
|
...value,
|
|
790
|
-
...(type === 'p256' ? { prehash: value.prehash } : {}),
|
|
854
|
+
...(type === 'p256' ? { prehash: (value as P256).prehash } : {}),
|
|
791
855
|
...(type === 'keychain'
|
|
792
856
|
? {
|
|
793
857
|
...(!(
|
|
@@ -827,10 +891,24 @@ export declare namespace from {
|
|
|
827
891
|
payload?: Hex.Hex | Bytes.Bytes | undefined
|
|
828
892
|
}
|
|
829
893
|
|
|
894
|
+
/**
|
|
895
|
+
* Multisig envelope input variant where `account` and `genesisConfigId` are derived
|
|
896
|
+
* from the supplied `genesisConfig`. Pass `init: true` to opt into bootstrap
|
|
897
|
+
* (uses `genesisConfig` as the bootstrap `init`); omit `init` for subsequent
|
|
898
|
+
* (non-bootstrap) transactions.
|
|
899
|
+
*/
|
|
900
|
+
type MultisigFromGenesisConfig = {
|
|
901
|
+
type?: 'multisig' | undefined
|
|
902
|
+
genesisConfig: MultisigConfig.Config
|
|
903
|
+
signatures: readonly SignatureEnvelope[]
|
|
904
|
+
init?: MultisigConfig.Config | boolean | undefined
|
|
905
|
+
}
|
|
906
|
+
|
|
830
907
|
type Value =
|
|
831
908
|
| UnionPartialBy<SignatureEnvelope, 'prehash' | 'type'>
|
|
832
909
|
| Secp256k1Flat
|
|
833
910
|
| Serialized
|
|
911
|
+
| MultisigFromGenesisConfig
|
|
834
912
|
|
|
835
913
|
type ReturnValue<value extends Value> = Compute<
|
|
836
914
|
OneOf<
|
|
@@ -838,16 +916,18 @@ export declare namespace from {
|
|
|
838
916
|
? SignatureEnvelope
|
|
839
917
|
: value extends Secp256k1Flat
|
|
840
918
|
? Secp256k1
|
|
841
|
-
:
|
|
842
|
-
?
|
|
843
|
-
:
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
919
|
+
: value extends MultisigFromGenesisConfig
|
|
920
|
+
? Multisig
|
|
921
|
+
: IsNarrowable<value, SignatureEnvelope> extends true
|
|
922
|
+
? SignatureEnvelope
|
|
923
|
+
: Assign<
|
|
924
|
+
value,
|
|
925
|
+
{
|
|
926
|
+
readonly type: GetType<value>
|
|
927
|
+
} & (GetType<value> extends 'keychain'
|
|
928
|
+
? { keyId?: Address.Address | undefined }
|
|
929
|
+
: {})
|
|
930
|
+
>
|
|
851
931
|
>
|
|
852
932
|
>
|
|
853
933
|
}
|
|
@@ -963,7 +1043,9 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
|
|
|
963
1043
|
return {
|
|
964
1044
|
type: 'multisig',
|
|
965
1045
|
account: multisig.account,
|
|
966
|
-
configId
|
|
1046
|
+
// Map RPC wire field `configId` (TIP-1061 spec name) to the typed
|
|
1047
|
+
// envelope's `genesisConfigId`.
|
|
1048
|
+
genesisConfigId: multisig.configId,
|
|
967
1049
|
// Owner approvals are raw serialized signatures (node `Vec<Bytes>`).
|
|
968
1050
|
signatures: multisig.signatures.map((signature) =>
|
|
969
1051
|
deserialize(signature),
|
|
@@ -1050,8 +1132,8 @@ export function getType<
|
|
|
1050
1132
|
|
|
1051
1133
|
// Detect Multisig signature
|
|
1052
1134
|
if (
|
|
1053
|
-
'account' in envelope &&
|
|
1054
|
-
|
|
1135
|
+
(('account' in envelope && 'genesisConfigId' in envelope) ||
|
|
1136
|
+
'genesisConfig' in envelope) &&
|
|
1055
1137
|
'signatures' in envelope
|
|
1056
1138
|
)
|
|
1057
1139
|
return 'multisig' as never
|
|
@@ -1151,7 +1233,7 @@ export function serialize(
|
|
|
1151
1233
|
|
|
1152
1234
|
if (type === 'multisig') {
|
|
1153
1235
|
const multisig = envelope as Multisig
|
|
1154
|
-
// Format: `0x05 || rlp([account,
|
|
1236
|
+
// Format: `0x05 || rlp([account, genesisConfigId, signatures, init])`, where each
|
|
1155
1237
|
// owner approval is an encoded primitive signature. `init` is the bootstrap
|
|
1156
1238
|
// config (an RLP list) when present, otherwise the canonical empty-string
|
|
1157
1239
|
// placeholder (`0x` → RLP `0x80`).
|
|
@@ -1159,7 +1241,7 @@ export function serialize(
|
|
|
1159
1241
|
serializedMultisigType,
|
|
1160
1242
|
Rlp.fromHex([
|
|
1161
1243
|
multisig.account,
|
|
1162
|
-
multisig.
|
|
1244
|
+
multisig.genesisConfigId,
|
|
1163
1245
|
multisig.signatures.map((signature) => serialize(signature)),
|
|
1164
1246
|
multisig.init ? MultisigConfig.toTuple(multisig.init) : '0x',
|
|
1165
1247
|
]),
|
|
@@ -1191,33 +1273,33 @@ export declare namespace serialize {
|
|
|
1191
1273
|
* recovered owner address. Works for any owner key type (secp256k1, p256,
|
|
1192
1274
|
* webAuthn, keychain).
|
|
1193
1275
|
*
|
|
1276
|
+
* Config updates never change `account`/`genesisConfigId`, so the genesis
|
|
1277
|
+
* config is the correct input even for post-update transactions.
|
|
1278
|
+
*
|
|
1194
1279
|
* @example
|
|
1195
1280
|
* ```ts twoslash
|
|
1196
1281
|
* import { Secp256k1 } from 'ox'
|
|
1197
1282
|
* import { MultisigConfig, SignatureEnvelope, TxEnvelopeTempo } from 'ox/tempo'
|
|
1198
1283
|
*
|
|
1199
|
-
* const
|
|
1284
|
+
* const genesisConfig = MultisigConfig.from({
|
|
1200
1285
|
* threshold: 2,
|
|
1201
1286
|
* owners: [
|
|
1202
1287
|
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
1203
1288
|
* { owner: '0x2222222222222222222222222222222222222222', weight: 1 },
|
|
1204
1289
|
* ],
|
|
1205
1290
|
* })
|
|
1206
|
-
* const configId = MultisigConfig.toId(config)
|
|
1207
|
-
* const account = MultisigConfig.getAddress({ configId })
|
|
1208
1291
|
*
|
|
1209
1292
|
* const tx = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
|
|
1210
1293
|
* const payload = TxEnvelopeTempo.getSignPayload(tx)
|
|
1211
|
-
* const digest = MultisigConfig.getSignPayload({ payload, account, configId })
|
|
1212
1294
|
*
|
|
1213
1295
|
* const privateKeys = [Secp256k1.randomPrivateKey(), Secp256k1.randomPrivateKey()]
|
|
1296
|
+
* const digest = MultisigConfig.getSignPayload({ payload, genesisConfig })
|
|
1214
1297
|
* const signatures = privateKeys.map((privateKey) =>
|
|
1215
1298
|
* SignatureEnvelope.from(Secp256k1.sign({ payload: digest, privateKey })),
|
|
1216
1299
|
* )
|
|
1217
1300
|
*
|
|
1218
1301
|
* const ordered = SignatureEnvelope.sortMultisigApprovals({ // [!code focus]
|
|
1219
|
-
*
|
|
1220
|
-
* configId, // [!code focus]
|
|
1302
|
+
* genesisConfig, // [!code focus]
|
|
1221
1303
|
* payload, // [!code focus]
|
|
1222
1304
|
* signatures, // [!code focus]
|
|
1223
1305
|
* }) // [!code focus]
|
|
@@ -1229,12 +1311,17 @@ export declare namespace serialize {
|
|
|
1229
1311
|
export function sortMultisigApprovals(
|
|
1230
1312
|
value: sortMultisigApprovals.Value,
|
|
1231
1313
|
): readonly SignatureEnvelope[] {
|
|
1232
|
-
const {
|
|
1233
|
-
const digest = MultisigConfig.getSignPayload(
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1314
|
+
const { payload, signatures } = value
|
|
1315
|
+
const digest = MultisigConfig.getSignPayload(
|
|
1316
|
+
'genesisConfig' in value && value.genesisConfig
|
|
1317
|
+
? { payload, genesisConfig: value.genesisConfig }
|
|
1318
|
+
: {
|
|
1319
|
+
payload,
|
|
1320
|
+
account: (value as { account: Address.Address }).account,
|
|
1321
|
+
genesisConfigId: (value as { genesisConfigId: Hex.Hex })
|
|
1322
|
+
.genesisConfigId,
|
|
1323
|
+
},
|
|
1324
|
+
)
|
|
1238
1325
|
// Recover each signer once (decorate–sort–undecorate) rather than inside the
|
|
1239
1326
|
// comparator.
|
|
1240
1327
|
return signatures
|
|
@@ -1248,15 +1335,26 @@ export function sortMultisigApprovals(
|
|
|
1248
1335
|
|
|
1249
1336
|
export declare namespace sortMultisigApprovals {
|
|
1250
1337
|
type Value = {
|
|
1251
|
-
/** The native multisig account address. */
|
|
1252
|
-
account: Address.Address
|
|
1253
|
-
/** The permanent config ID. */
|
|
1254
|
-
configId: Hex.Hex
|
|
1255
1338
|
/** The inner transaction sign payload (`tx.signature_hash()`). */
|
|
1256
1339
|
payload: Hex.Hex | Bytes.Bytes
|
|
1257
1340
|
/** The primitive owner approvals to order. */
|
|
1258
1341
|
signatures: readonly SignatureEnvelope[]
|
|
1259
|
-
}
|
|
1342
|
+
} & OneOf<
|
|
1343
|
+
| {
|
|
1344
|
+
/** The native multisig account address. */
|
|
1345
|
+
account: Address.Address
|
|
1346
|
+
/** The permanent config ID. */
|
|
1347
|
+
genesisConfigId: Hex.Hex
|
|
1348
|
+
}
|
|
1349
|
+
| {
|
|
1350
|
+
/**
|
|
1351
|
+
* The initial multisig config (the bootstrap config that derived the
|
|
1352
|
+
* permanent `account` and `genesisConfigId`). Used to derive both values
|
|
1353
|
+
* automatically.
|
|
1354
|
+
*/
|
|
1355
|
+
genesisConfig: MultisigConfig.Config
|
|
1356
|
+
}
|
|
1357
|
+
>
|
|
1260
1358
|
|
|
1261
1359
|
type ErrorType =
|
|
1262
1360
|
| MultisigConfig.getSignPayload.ErrorType
|
|
@@ -1336,7 +1434,9 @@ export function toRpc(envelope: SignatureEnvelope): SignatureEnvelopeRpc {
|
|
|
1336
1434
|
return {
|
|
1337
1435
|
type: 'multisig',
|
|
1338
1436
|
account: multisig.account,
|
|
1339
|
-
|
|
1437
|
+
// Map the typed envelope's `genesisConfigId` to the RPC wire field
|
|
1438
|
+
// `configId` (TIP-1061 spec name).
|
|
1439
|
+
configId: multisig.genesisConfigId,
|
|
1340
1440
|
// Owner approvals are raw serialized signatures (node `Vec<Bytes>`).
|
|
1341
1441
|
signatures: multisig.signatures.map((signature) => serialize(signature)),
|
|
1342
1442
|
...(multisig.init ? { init: multisig.init } : {}),
|