Skip to content

Commit

Permalink
[ts-sdk] cleanup some deprecated multisig methods (MystenLabs#14832)
Browse files Browse the repository at this point in the history
## Description 

Describe the changes or additions included in this PR.

## Test Plan 

How did you test the new or updated feature?

---
If your changes are not user-facing and not a breaking change, you can
skip the following section. Otherwise, please indicate what changed, and
then add to the Release Notes section as highlighted during the release
process.

### Type of Change (Check all that apply)

- [ ] protocol change
- [ ] user-visible impact
- [ ] breaking change for a client SDKs
- [ ] breaking change for FNs (FN binary must upgrade)
- [ ] breaking change for validators or node operators (must upgrade
binaries)
- [ ] breaking change for on-chain data layout
- [ ] necessitate either a data wipe or data migration

### Release notes
  • Loading branch information
hayes-mysten authored Nov 14, 2023
1 parent 1c2486a commit 652bcdd
Show file tree
Hide file tree
Showing 6 changed files with 598 additions and 1,347 deletions.
5 changes: 5 additions & 0 deletions .changeset/two-rats-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@mysten/sui.js': patch
---

Remove some multisig methods that had previously been deprecated and are no longer exported
182 changes: 3 additions & 179 deletions sdk/typescript/src/cryptography/multisig.ts
Original file line number Diff line number Diff line change
@@ -1,151 +1,15 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { fromB64, toB64 } from '@mysten/bcs';
import { blake2b } from '@noble/hashes/blake2b';
import { bytesToHex } from '@noble/hashes/utils';
import { fromB64 } from '@mysten/bcs';

import { bcs } from '../bcs/index.js';
import { Ed25519PublicKey } from '../keypairs/ed25519/publickey.js';
import { Secp256k1PublicKey } from '../keypairs/secp256k1/publickey.js';
import { Secp256r1PublicKey } from '../keypairs/secp256r1/publickey.js';
import { MAX_SIGNER_IN_MULTISIG, MIN_SIGNER_IN_MULTISIG } from '../multisig/publickey.js';
import { normalizeSuiAddress } from '../utils/sui-types.js';
import type { PublicKey } from './publickey.js';
import { SIGNATURE_SCHEME_TO_FLAG } from './signature-scheme.js';
import type { SignatureScheme } from './signature-scheme.js';
import type { SerializedSignature } from './signature.js';
import type { SignaturePubkeyPair } from './utils.js';
// eslint-disable-next-line import/no-cycle
import { toSingleSignaturePubkeyPair } from './utils.js';

export { MAX_SIGNER_IN_MULTISIG, MIN_SIGNER_IN_MULTISIG } from '../multisig/publickey.js';

export type PubkeyWeightPair = {
pubKey: PublicKey;
weight: number;
};

export type CompressedSignature =
| { ED25519: number[] }
| { Secp256k1: number[] }
| { Secp256r1: number[] };

export type PublicKeyEnum =
| { ED25519: number[] }
| { Secp256k1: number[] }
| { Secp256r1: number[] };

export type PubkeyEnumWeightPair = {
pubKey: PublicKeyEnum;
weight: number;
};

export type MultiSigPublicKey = {
pk_map: PubkeyEnumWeightPair[];
threshold: number;
};

export type MultiSig = {
sigs: CompressedSignature[];
bitmap: number;
multisig_pk: MultiSigPublicKey;
};

/// Derives a multisig address from a list of pk and weights and threshold.
// It is the 32-byte Blake2b hash of the serializd bytes of `flag_MultiSig || threshold || flag_1 || pk_1 || weight_1
/// || ... || flag_n || pk_n || weight_n`
export function toMultiSigAddress(pks: PubkeyWeightPair[], threshold: number): string {
if (pks.length > MAX_SIGNER_IN_MULTISIG) {
throw new Error(`Max number of signers in a multisig is ${MAX_SIGNER_IN_MULTISIG}`);
}

if (pks.length < MIN_SIGNER_IN_MULTISIG) {
throw new Error(`Min number of signers in a multisig is ${MIN_SIGNER_IN_MULTISIG}`);
}

// max length = 1 flag byte + (max pk size + max weight size (u8)) * max signer size + 2 threshold bytes (u16)
let maxLength = 1 + (64 + 1) * MAX_SIGNER_IN_MULTISIG + 2;
let tmp = new Uint8Array(maxLength);
tmp.set([SIGNATURE_SCHEME_TO_FLAG['MultiSig']]);

let arr = to_uint8array(threshold);
tmp.set(arr, 1);
// The initial value 3 ensures that following data will be after the flag byte and threshold bytes
let i = 3;
let totalWeight = 0;
const seenPubKeys = new Set<PublicKey>();

for (const pk of pks) {
if (pk.weight < 1 || pk.weight > 255) {
throw new Error(`Invalid weight`);
}
if (seenPubKeys.has(pk.pubKey)) {
throw new Error(`Multisig does not support duplicate public keys`);
}
seenPubKeys.add(pk.pubKey);

totalWeight += pk.weight;
tmp.set([pk.pubKey.flag()], i);
tmp.set(pk.pubKey.toRawBytes(), i + 1);
tmp.set([pk.weight], i + 1 + pk.pubKey.toRawBytes().length);
i += pk.pubKey.toRawBytes().length + 2;
}
if (threshold > totalWeight) {
throw new Error(`Unreachable threshold`);
}
return normalizeSuiAddress(bytesToHex(blake2b(tmp.slice(0, i), { dkLen: 32 })));
}

/// Combine a list of serialized sigs, a list of pk weight pairs
/// and threshold into a single multisig. `sigs` are required to
/// be in the same order as `pks`. e.g. for [pk1, pk2, pk3, pk4, pk5],
/// [sig1, sig2, sig5] is valid, but [sig2, sig1, sig5] is invalid.
export function combinePartialSigs(
sigs: SerializedSignature[],
pks: PubkeyWeightPair[],
threshold: number,
): SerializedSignature {
if (sigs.length > MAX_SIGNER_IN_MULTISIG) {
throw new Error(`Max number of signatures in a multisig is ${MAX_SIGNER_IN_MULTISIG}`);
}

let multisig_pk: MultiSigPublicKey = {
pk_map: pks.map((x) => toPkWeightPair(x)),
threshold: threshold,
};

let bitmap = 0;
let compressed_sigs: CompressedSignature[] = new Array(sigs.length);
for (let i = 0; i < sigs.length; i++) {
let parsed = toSingleSignaturePubkeyPair(sigs[i]);
let bytes = Array.from(parsed.signature.map((x) => Number(x)));
if (parsed.signatureScheme === 'ED25519') {
compressed_sigs[i] = { ED25519: bytes };
} else if (parsed.signatureScheme === 'Secp256k1') {
compressed_sigs[i] = { Secp256k1: bytes };
} else if (parsed.signatureScheme === 'Secp256r1') {
compressed_sigs[i] = { Secp256r1: bytes };
}
for (let j = 0; j < pks.length; j++) {
if (parsed.pubKey.equals(pks[j].pubKey)) {
bitmap |= 1 << j;
break;
}
}
}
let multisig: MultiSig = {
sigs: compressed_sigs,
bitmap,
multisig_pk,
};

const bytes = bcs.MultiSig.serialize(multisig).toBytes();
let tmp = new Uint8Array(bytes.length + 1);
tmp.set([SIGNATURE_SCHEME_TO_FLAG['MultiSig']]);
tmp.set(bytes, 1);
return toB64(tmp);
}

/// Decode a multisig signature into a list of signatures, public keys and flags.
export function decodeMultiSig(signature: string): SignaturePubkeyPair[] {
Expand All @@ -154,10 +18,10 @@ export function decodeMultiSig(signature: string): SignaturePubkeyPair[] {
throw new Error('Invalid MultiSig flag');
}

const multisig: MultiSig = bcs.MultiSig.parse(parsed.slice(1));
const multisig = bcs.MultiSig.parse(parsed.slice(1));
let res: SignaturePubkeyPair[] = new Array(multisig.sigs.length);
for (let i = 0; i < multisig.sigs.length; i++) {
let s: CompressedSignature = multisig.sigs[i];
let s = multisig.sigs[i];
let pk_index = as_indices(multisig.bitmap).at(i);
let pk_bytes = Object.values(multisig.multisig_pk.pk_map[pk_index as number].pubKey)[0];
const scheme = Object.keys(s)[0] as SignatureScheme;
Expand Down Expand Up @@ -188,46 +52,6 @@ export function decodeMultiSig(signature: string): SignaturePubkeyPair[] {
return res;
}

function toPkWeightPair(pair: PubkeyWeightPair): PubkeyEnumWeightPair {
let pk_bytes = Array.from(pair.pubKey.toRawBytes().map((x) => Number(x)));
switch (pair.pubKey.flag()) {
case SIGNATURE_SCHEME_TO_FLAG['Secp256k1']:
return {
pubKey: {
Secp256k1: pk_bytes,
},
weight: pair.weight,
};
case SIGNATURE_SCHEME_TO_FLAG['Secp256r1']:
return {
pubKey: {
Secp256r1: pk_bytes,
},
weight: pair.weight,
};
case SIGNATURE_SCHEME_TO_FLAG['ED25519']:
return {
pubKey: {
ED25519: pk_bytes,
},
weight: pair.weight,
};
default:
throw new Error('Unsupported signature scheme');
}
}

/// Convert u16 to Uint8Array of length 2 in little endian.
function to_uint8array(threshold: number): Uint8Array {
if (threshold < 1 || threshold > 65535) {
throw new Error('Invalid threshold');
}
let arr = new Uint8Array(2);
arr[0] = threshold & 0xff;
arr[1] = threshold >> 8;
return arr;
}

function as_indices(bitmap: number): Uint8Array {
if (bitmap < 0 || bitmap > 1024) {
throw new Error('Invalid bitmap');
Expand Down
Loading

0 comments on commit 652bcdd

Please sign in to comment.