@mysten/sui
Version:
Sui TypeScript API
192 lines (190 loc) • 7.9 kB
JavaScript
import { normalizeSuiAddress } from "../utils/sui-types.mjs";
import { bcs as suiBcs } from "../bcs/index.mjs";
import { SIGNATURE_FLAG_TO_SCHEME, SIGNATURE_SCHEME_TO_FLAG } from "../cryptography/signature-scheme.mjs";
import { PublicKey, bytesEqual } from "../cryptography/publickey.mjs";
import { toZkLoginPublicIdentifier } from "../zklogin/publickey.mjs";
import { parseSerializedSignature } from "../cryptography/signature.mjs";
import { MultiSigSigner } from "./signer.mjs";
import { publicKeyFromRawBytes } from "../verify/verify.mjs";
import { fromBase64, toBase64 } from "@mysten/bcs";
import { blake2b } from "@noble/hashes/blake2.js";
import { bytesToHex } from "@noble/hashes/utils.js";
//#region src/multisig/publickey.ts
const MAX_SIGNER_IN_MULTISIG = 10;
const MIN_SIGNER_IN_MULTISIG = 1;
/**
* A MultiSig public key
*/
var MultiSigPublicKey = class MultiSigPublicKey extends PublicKey {
/**
* Create a new MultiSigPublicKey object
*/
constructor(value, options = {}) {
super();
if (typeof value === "string") {
this.rawBytes = fromBase64(value);
this.multisigPublicKey = suiBcs.MultiSigPublicKey.parse(this.rawBytes);
} else if (value instanceof Uint8Array) {
this.rawBytes = value;
this.multisigPublicKey = suiBcs.MultiSigPublicKey.parse(this.rawBytes);
} else {
this.multisigPublicKey = value;
this.rawBytes = suiBcs.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 }) => {
return {
pubKey: { [SIGNATURE_FLAG_TO_SCHEME[publicKey.flag()]]: 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 + 65 * MAX_SIGNER_IN_MULTISIG + 2;
const tmp = new Uint8Array(maxLength);
tmp.set([SIGNATURE_SCHEME_TO_FLAG["MultiSig"]]);
tmp.set(suiBcs.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(suiBcs.MultiSigPublicKey.serialize(this.multisigPublicKey).toBytes(), suiBcs.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++) {
const 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, { legacyAddress: false }).toRawBytes();
else publicKey = parsed.publicKey;
compressedSignatures[i] = { [parsed.signatureScheme]: parsed.signature };
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;
}
const multisig = {
sigs: compressedSignatures,
bitmap,
multisig_pk: this.multisigPublicKey
};
const bytes = suiBcs.MultiSig.serialize(multisig, { maxSize: 8192 }).toBytes();
const tmp = new Uint8Array(bytes.length + 1);
tmp.set([SIGNATURE_SCHEME_TO_FLAG["MultiSig"]]);
tmp.set(bytes, 1);
return toBase64(tmp);
}
};
/**
* Parse multisig structure into an array of individual signatures: signature scheme, the actual individual signature, public key and its weight.
*/
function parsePartialSignatures(multisig, options = {}) {
const 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");
const res = [];
for (let i = 0; i < 10; i++) if ((bitmap & 1 << i) !== 0) res.push(i);
return Uint8Array.from(res);
}
//#endregion
export { MultiSigPublicKey, parsePartialSignatures };
//# sourceMappingURL=publickey.mjs.map