UNPKG

@mysten/sui

Version:

Sui TypeScript API(Work in Progress)

244 lines (243 loc) 8.44 kB
import { fromB64, toB64 } from "@mysten/bcs"; import { blake2b } from "@noble/hashes/blake2b"; import { bytesToHex } from "@noble/hashes/utils"; import { bcs } from "../bcs/index.js"; import { bytesEqual, PublicKey } from "../cryptography/publickey.js"; import { SIGNATURE_FLAG_TO_SCHEME, SIGNATURE_SCHEME_TO_FLAG } from "../cryptography/signature-scheme.js"; import { parseSerializedSignature } from "../cryptography/signature.js"; import { normalizeSuiAddress } from "../utils/sui-types.js"; import { publicKeyFromRawBytes } from "../verify/index.js"; import { toZkLoginPublicIdentifier } from "../zklogin/publickey.js"; import { MultiSigSigner } from "./signer.js"; const MAX_SIGNER_IN_MULTISIG = 10; const MIN_SIGNER_IN_MULTISIG = 1; class MultiSigPublicKey extends PublicKey { /** * Create a new MultiSigPublicKey object */ constructor(value, options = {}) { super(); if (typeof value === "string") { this.rawBytes = fromB64(value); this.multisigPublicKey = bcs.MultiSigPublicKey.parse(this.rawBytes); } else if (value instanceof Uint8Array) { this.rawBytes = value; this.multisigPublicKey = bcs.MultiSigPublicKey.parse(this.rawBytes); } else { this.multisigPublicKey = value; this.rawBytes = bcs.MultiSigPublicKey.serialize(value).toBytes(); } if (this.multisigPublicKey.threshold < 1) { throw new Error("Invalid threshold"); } const seenPublicKeys = /* @__PURE__ */ new Set(); this.publicKeys = this.multisigPublicKey.pk_map.map(({ pubKey, weight }) => { const [scheme, bytes] = Object.entries(pubKey).filter(([name]) => name !== "$kind")[0]; const publicKeyStr = Uint8Array.from(bytes).toString(); if (seenPublicKeys.has(publicKeyStr)) { throw new Error(`Multisig does not support duplicate public keys`); } seenPublicKeys.add(publicKeyStr); if (weight < 1) { throw new Error(`Invalid weight`); } return { publicKey: publicKeyFromRawBytes(scheme, Uint8Array.from(bytes), options), weight }; }); const totalWeight = this.publicKeys.reduce((sum, { weight }) => sum + weight, 0); if (this.multisigPublicKey.threshold > totalWeight) { throw new Error(`Unreachable threshold`); } if (this.publicKeys.length > MAX_SIGNER_IN_MULTISIG) { throw new Error(`Max number of signers in a multisig is ${MAX_SIGNER_IN_MULTISIG}`); } if (this.publicKeys.length < MIN_SIGNER_IN_MULTISIG) { throw new Error(`Min number of signers in a multisig is ${MIN_SIGNER_IN_MULTISIG}`); } } /** * A static method to create a new MultiSig publickey instance from a set of public keys and their associated weights pairs and threshold. */ static fromPublicKeys({ threshold, publicKeys }) { return new MultiSigPublicKey({ pk_map: publicKeys.map(({ publicKey, weight }) => { const scheme = SIGNATURE_FLAG_TO_SCHEME[publicKey.flag()]; return { pubKey: { [scheme]: Array.from(publicKey.toRawBytes()) }, weight }; }), threshold }); } /** * Checks if two MultiSig public keys are equal */ equals(publicKey) { return super.equals(publicKey); } /** * Return the byte array representation of the MultiSig public key */ toRawBytes() { return this.rawBytes; } getPublicKeys() { return this.publicKeys; } getThreshold() { return this.multisigPublicKey.threshold; } getSigner(...signers) { return new MultiSigSigner(this, signers); } /** * Return the Sui address associated with this MultiSig public key */ toSuiAddress() { const maxLength = 1 + (64 + 1) * MAX_SIGNER_IN_MULTISIG + 2; const tmp = new Uint8Array(maxLength); tmp.set([SIGNATURE_SCHEME_TO_FLAG["MultiSig"]]); tmp.set(bcs.u16().serialize(this.multisigPublicKey.threshold).toBytes(), 1); let i = 3; for (const { publicKey, weight } of this.publicKeys) { const bytes = publicKey.toSuiBytes(); tmp.set(bytes, i); i += bytes.length; tmp.set([weight], i++); } return normalizeSuiAddress(bytesToHex(blake2b(tmp.slice(0, i), { dkLen: 32 }))); } /** * Return the Sui address associated with this MultiSig public key */ flag() { return SIGNATURE_SCHEME_TO_FLAG["MultiSig"]; } /** * Verifies that the signature is valid for for the provided message */ async verify(message, multisigSignature) { const parsed = parseSerializedSignature(multisigSignature); if (parsed.signatureScheme !== "MultiSig") { throw new Error("Invalid signature scheme"); } const { multisig } = parsed; let signatureWeight = 0; if (!bytesEqual( bcs.MultiSigPublicKey.serialize(this.multisigPublicKey).toBytes(), bcs.MultiSigPublicKey.serialize(multisig.multisig_pk).toBytes() )) { return false; } for (const { publicKey, weight, signature } of parsePartialSignatures(multisig)) { if (!await publicKey.verify(message, signature)) { return false; } signatureWeight += weight; } return signatureWeight >= this.multisigPublicKey.threshold; } /** * Combines multiple partial signatures into a single multisig, ensuring that each public key signs only once * and that all the public keys involved are known and valid, and then serializes multisig into the standard format */ combinePartialSignatures(signatures) { if (signatures.length > MAX_SIGNER_IN_MULTISIG) { throw new Error(`Max number of signatures in a multisig is ${MAX_SIGNER_IN_MULTISIG}`); } let bitmap = 0; const compressedSignatures = new Array(signatures.length); for (let i = 0; i < signatures.length; i++) { let parsed = parseSerializedSignature(signatures[i]); if (parsed.signatureScheme === "MultiSig") { throw new Error("MultiSig is not supported inside MultiSig"); } let publicKey; if (parsed.signatureScheme === "ZkLogin") { publicKey = toZkLoginPublicIdentifier( parsed.zkLogin?.addressSeed, parsed.zkLogin?.iss ).toRawBytes(); } else { publicKey = parsed.publicKey; } compressedSignatures[i] = { [parsed.signatureScheme]: Array.from(parsed.signature.map((x) => Number(x))) }; let publicKeyIndex; for (let j = 0; j < this.publicKeys.length; j++) { if (bytesEqual(publicKey, this.publicKeys[j].publicKey.toRawBytes())) { if (bitmap & 1 << j) { throw new Error("Received multiple signatures from the same public key"); } publicKeyIndex = j; break; } } if (publicKeyIndex === void 0) { throw new Error("Received signature from unknown public key"); } bitmap |= 1 << publicKeyIndex; } let multisig = { sigs: compressedSignatures, bitmap, multisig_pk: this.multisigPublicKey }; const bytes = bcs.MultiSig.serialize(multisig, { maxSize: 8192 }).toBytes(); let tmp = new Uint8Array(bytes.length + 1); tmp.set([SIGNATURE_SCHEME_TO_FLAG["MultiSig"]]); tmp.set(bytes, 1); return toB64(tmp); } } function parsePartialSignatures(multisig, options = {}) { let res = new Array(multisig.sigs.length); for (let i = 0; i < multisig.sigs.length; i++) { const [signatureScheme, signature] = Object.entries(multisig.sigs[i]).filter( ([name]) => name !== "$kind" )[0]; const pkIndex = asIndices(multisig.bitmap).at(i); const pair = multisig.multisig_pk.pk_map[pkIndex]; const pkBytes = Uint8Array.from(Object.values(pair.pubKey)[0]); if (signatureScheme === "MultiSig") { throw new Error("MultiSig is not supported inside MultiSig"); } const publicKey = publicKeyFromRawBytes(signatureScheme, pkBytes, options); res[i] = { signatureScheme, signature: Uint8Array.from(signature), publicKey, weight: pair.weight }; } return res; } function asIndices(bitmap) { if (bitmap < 0 || bitmap > 1024) { throw new Error("Invalid bitmap"); } let res = []; for (let i = 0; i < 10; i++) { if ((bitmap & 1 << i) !== 0) { res.push(i); } } return Uint8Array.from(res); } export { MAX_SIGNER_IN_MULTISIG, MIN_SIGNER_IN_MULTISIG, MultiSigPublicKey, parsePartialSignatures }; //# sourceMappingURL=publickey.js.map