From 8a9a9bcc28ace65cde46eed5010eb8927374a736 Mon Sep 17 00:00:00 2001 From: Omar Khan Date: Tue, 13 Sep 2022 13:50:54 -0400 Subject: [PATCH] add ExpandedSignerList amendment support (#2026) expand the maximum signer list to 32 entries --- packages/xrpl/HISTORY.md | 5 +- packages/xrpl/src/models/common/index.ts | 21 +++ .../src/models/transactions/signerListSet.ts | 22 +++- packages/xrpl/test/models/signerListSet.ts | 123 ++++++++++++++++++ 4 files changed, 166 insertions(+), 5 deletions(-) diff --git a/packages/xrpl/HISTORY.md b/packages/xrpl/HISTORY.md index 07f47ac0..af584b3e 100644 --- a/packages/xrpl/HISTORY.md +++ b/packages/xrpl/HISTORY.md @@ -2,11 +2,12 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xrpl-announce) for release announcements. We recommend that xrpl.js (ripple-lib) users stay up-to-date with the latest stable release. ## Unreleased +### Added +* Support for ExpandedSignerList amendment that expands the maximum signer list to 32 entries. ## 2.4.0 (2022-09-01) ### Added - -- Export `verify` from ripple-keypairs as `verifyKeypairSignature` for use in web-apps. +* Export `verify` from ripple-keypairs as `verifyKeypairSignature` for use in web-apps. ### Fixed * `Wallet.fromMnemonic` now allows lowercase for RFC1751 mnemonics (#2046) diff --git a/packages/xrpl/src/models/common/index.ts b/packages/xrpl/src/models/common/index.ts index 7881af45..cd68d760 100644 --- a/packages/xrpl/src/models/common/index.ts +++ b/packages/xrpl/src/models/common/index.ts @@ -61,10 +61,31 @@ interface PathStep { export type Path = PathStep[] +/** + * The object that describes the signer in SignerEntries. + */ export interface SignerEntry { + /** + * The object that describes the signer in SignerEntries. + */ SignerEntry: { + /** + * An XRP Ledger address whose signature contributes to the multi-signature. + * It does not need to be a funded address in the ledger. + */ Account: string + /** + * The weight of a signature from this signer. + * A multi-signature is only valid if the sum weight of the signatures provided meets + * or exceeds the signer list's SignerQuorum value. + */ SignerWeight: number + /** + * An arbitrary 256-bit (32-byte) field that can be used to identify the signer, which + * may be useful for smart contracts, or for identifying who controls a key in a large + * organization. + */ + WalletLocator?: string } } diff --git a/packages/xrpl/src/models/transactions/signerListSet.ts b/packages/xrpl/src/models/transactions/signerListSet.ts index c7dd3c3b..438b27bb 100644 --- a/packages/xrpl/src/models/transactions/signerListSet.ts +++ b/packages/xrpl/src/models/transactions/signerListSet.ts @@ -20,13 +20,15 @@ export interface SignerListSet extends BaseTransaction { /** * Array of SignerEntry objects, indicating the addresses and weights of * signers in this list. This signer list must have at least 1 member and no - * more than 8 members. No address may appear more than once in the list, nor + * more than 32 members. No address may appear more than once in the list, nor * may the Account submitting the transaction appear in the list. */ SignerEntries: SignerEntry[] } -const MAX_SIGNERS = 8 +const MAX_SIGNERS = 32 + +const HEX_WALLET_LOCATOR_REGEX = /^[0-9A-Fa-f]{64}$/u /** * Verify the form and type of an SignerListSet at runtime. @@ -61,7 +63,21 @@ export function validateSignerListSet(tx: Record): void { if (tx.SignerEntries.length > MAX_SIGNERS) { throw new ValidationError( - 'SignerListSet: maximum of 8 members allowed in SignerEntries', + `SignerListSet: maximum of ${MAX_SIGNERS} members allowed in SignerEntries`, ) } + + for (const entry of tx.SignerEntries) { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be a SignerEntry + const signerEntry = entry as SignerEntry + const { WalletLocator } = signerEntry.SignerEntry + if ( + WalletLocator !== undefined && + !HEX_WALLET_LOCATOR_REGEX.test(WalletLocator) + ) { + throw new ValidationError( + `SignerListSet: WalletLocator in SignerEntry must be a 256-bit (32-byte) hexadecimal value`, + ) + } + } } diff --git a/packages/xrpl/test/models/signerListSet.ts b/packages/xrpl/test/models/signerListSet.ts index 18d6563d..ccbac3a3 100644 --- a/packages/xrpl/test/models/signerListSet.ts +++ b/packages/xrpl/test/models/signerListSet.ts @@ -89,4 +89,127 @@ describe('SignerListSet', function () { 'SignerListSet: invalid SignerEntries', ) }) + + it(`throws w/ maximum of 32 members allowed in SignerEntries`, function () { + signerListSetTx.SignerEntries = [] + const accounts = [ + 'rBFBipte4nAQCTsRxd2czwvSurhCpAf4X6', + 'r3ijUH32iiy9tYNj3rD7hKWYjy1BFUxngm', + 'rpwq8vi4Mn3L5kDJmb8Mg59CanPFPzMCnj', + 'rB72Gzqfejai46nkA4HaKYBHwAnn2yUoT4', + 'rGqsJSAW71pCfUwDD5m52bLw69RzFg6kMW', + 'rs8smPRA31Ym4mGxb1wzgwxtU5eVK82Gyk', + 'rLrugpGxzezUQLDh7Jv1tZpouuV4MQLbU9', + 'rUQ6zLXQdh1jJLGwMXp9P8rgi42kwuafzs', + 'rMjY8sPdfxsyRrnVKQcutxr4mTHNXy9dEF', + 'rUaxYLeFGm6SmMoa2WCqLKSyHwJyvaQmeG', + 'r9wUfeVtqMfqrcDTfCpNYbNZvs5q9M9Rpo', + 'rQncVNak5kvJGPUFa6fuKH7t8Usjs7Np1c', + 'rnwbSSnPbVbUzuBa4etkeYrfy5v7SyhtPu', + 'rDXh5D3t48MdBJyXByXq47k5P8Kuf1758B', + 'rh1D4jd2mAiqUPHfAZ2cY9Nbfa3kAkaQXP', + 'r9T129tXgtnyfGoLeS35c2HctaZAZSQoCH', + 'rUd2uKsyCWfJP7Ve36mKoJbNCA7RYThnYk', + 'r326x8PaAFtnaH7uoxaKrcDWuwpeHn4wDa', + 'rpN3mkXkYhfNadcXPrY4LniM1KpM3egyQM', + 'rsPKbR155hz1zrA4pSJp5Y2fxasZAatcHb', + 'rsyWFLaEKTpaoSJusjpcDvGexuHCwMnqss', + 'rUbc5RXfyF81oLDMgd3d7jpY9YMNMZG4XN', + 'rGpYHM88BZe1iVKFHm5xiWYYxR74oxJEXf', + 'rPsetWAtR1KxDtxzgHjRMD7Rc87rvXk5nD', + 'rwSeNhL6Hi34igr12mCr61jY42psfTkWTq', + 'r46Mygy98qjkDhVB6qs4sBnqaf7FPiA2vU', + 'r4s8GmeYN4CiwVate1nMUvwMQbundqf5cW', + 'rKAr4dQWDYG8cG2hSwJUVp4ry4WNaWiNgp', + 'rPWXRLp1vqeUHEH3WiSKuyo9GM9XhaENQU', + 'rPgmdBdRKGmndxNEYxUrrsYCZaS6go9RvW', + 'rPDJZ9irzgwKRKScfEmuJMvUgrqZAJNCbL', + 'rDuU2uSXMfEaoxN1qW8sj7aUNFLGEn3Hr2', + 'rsbjSjA4TCB9gtm7x7SrWbZHB6g4tt9CGU', + ] + signerListSetTx.SignerQuorum = accounts.length + for (const acc of accounts) { + signerListSetTx.SignerEntries.push({ + SignerEntry: { + Account: acc, + SignerWeight: 1, + }, + }) + } + + const errorMessage = + 'SignerListSet: maximum of 32 members allowed in SignerEntries' + assert.throws( + () => validateSignerListSet(signerListSetTx), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(signerListSetTx), + ValidationError, + errorMessage, + ) + }) + + it(`verifies valid WalletLocator in SignerEntries`, function () { + signerListSetTx.SignerQuorum = 3 + signerListSetTx.SignerEntries = [ + { + SignerEntry: { + Account: 'rBFBipte4nAQCTsRxd2czwvSurhCpAf4X6', + SignerWeight: 1, + WalletLocator: + 'CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE', + }, + }, + { + SignerEntry: { + Account: 'r3ijUH32iiy9tYNj3rD7hKWYjy1BFUxngm', + SignerWeight: 1, + }, + }, + { + SignerEntry: { + Account: 'rpwq8vi4Mn3L5kDJmb8Mg59CanPFPzMCnj', + SignerWeight: 1, + WalletLocator: + '00000000000000000000000000000000000000000000000000000000DEADBEEF', + }, + }, + ] + + assert.doesNotThrow(() => validateSignerListSet(signerListSetTx)) + assert.doesNotThrow(() => validate(signerListSetTx)) + }) + + it(`throws w/ invalid WalletLocator in SignerEntries`, function () { + signerListSetTx.SignerQuorum = 2 + signerListSetTx.SignerEntries = [ + { + SignerEntry: { + Account: 'rBFBipte4nAQCTsRxd2czwvSurhCpAf4X6', + SignerWeight: 1, + WalletLocator: 'not_valid', + }, + }, + { + SignerEntry: { + Account: 'r3ijUH32iiy9tYNj3rD7hKWYjy1BFUxngm', + SignerWeight: 1, + }, + }, + ] + const errorMessage = + 'SignerListSet: WalletLocator in SignerEntry must be a 256-bit (32-byte) hexadecimal value' + assert.throws( + () => validateSignerListSet(signerListSetTx), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(signerListSetTx), + ValidationError, + errorMessage, + ) + }) })