import { SerializedSignature } from '@mysten/sui.js/cryptography'; import { Ed25519PublicKey } from '@mysten/sui.js/keypairs/ed25519'; import { MultiSigPublicKey } from '@mysten/sui.js/multisig'; import { AccountConfig } from '@/constants'; import { MultiSigConfig } from '@/types'; /** * Represents a raw multi-signature account. */ export class MultiSigAccount { public publicKey: MultiSigPublicKey; constructor(private readonly config: MultiSigConfig) { MultiSigAccount.validate(config); this.publicKey = this.toMultisigPublicKey(config); } get address() { return this.publicKey.toSuiAddress(); } get publicKeys() { return this.publicKey.getPublicKeys(); } combinePartialSignatures(signatures: SerializedSignature[]): SerializedSignature { return this.publicKey.combinePartialSignatures(signatures); } private toMultisigPublicKey(account: MultiSigConfig) { const { ownersWithWeight, threshold, creationNonce } = account; const publicKeys = ownersWithWeight.map((it) => ({ ...it })); if (creationNonce !== undefined) { publicKeys.push({ publicKey: this.makeNoncePublicKey(creationNonce), weight: AccountConfig.nonce.weight, }); } return MultiSigPublicKey.fromPublicKeys({ threshold, publicKeys }); } private makeNoncePublicKey(nonce: number) { const buffer = new ArrayBuffer(Ed25519PublicKey.SIZE); new TextEncoder().encodeInto(AccountConfig.nonce.prefix, new Uint8Array(buffer, 0, AccountConfig.nonce.maxSize)); const nonceView = new DataView(buffer, AccountConfig.nonce.maxSize, 4); nonceView.setUint32(0, nonce, true); return new Ed25519PublicKey(new Uint8Array(buffer)); } private static validate(config: MultiSigConfig) { config.ownersWithWeight.forEach((it) => { const { weight } = it; if (weight < AccountConfig.minWeight || weight > AccountConfig.maxWeight) { throw new Error(`Invalid weight: ${weight} (1-${AccountConfig.maxWeight})`); } }); const totalWeight = config.ownersWithWeight.reduce((s, it) => s + it.weight, 0); if (config.threshold < 1 || config.threshold > totalWeight) { throw new Error(`Invalid threshold: ${config.threshold} (1-${totalWeight})`); } const maxOwner = config.creationNonce === undefined ? AccountConfig.maxOwnerWithoutNonce : AccountConfig.maxOwnerWithNonce; if (config.ownersWithWeight.length > maxOwner) { throw new Error('Owner number bigger than upper capacity'); } const addressSet = new Set(config.ownersWithWeight.map((it) => it.publicKey.toSuiAddress())); if (addressSet.size !== config.ownersWithWeight.length) { throw new Error('Duplicate address detected'); } } }