|
| 1 | +import { Bytes, bytesConcat, bytesFrom } from "../../bytes/index.js"; |
| 2 | +import { Since, SinceLike } from "../../ckb/index.js"; |
| 3 | +import { codec, Entity } from "../../codec/index.js"; |
| 4 | +import { HASH_CKB_SHORT_LENGTH, hashCkb } from "../../hasher/index.js"; |
| 5 | +import { Hex, hexFrom, HexLike } from "../../hex/index.js"; |
| 6 | +import { numFrom, NumLike, numToBytes } from "../../num/index.js"; |
| 7 | +import { SECP256K1_SIGNATURE_LENGTH } from "./secp256k1Signing.js"; |
| 8 | + |
| 9 | +export type MultisigCkbWitnessLike = ( |
| 10 | + | { |
| 11 | + publicKeyHashes: HexLike[]; |
| 12 | + publicKeys?: undefined | null; |
| 13 | + } |
| 14 | + | { |
| 15 | + publicKeyHashes?: undefined | null; |
| 16 | + publicKeys: HexLike[]; |
| 17 | + } |
| 18 | +) & { |
| 19 | + threshold: NumLike; |
| 20 | + mustMatch?: NumLike | null; |
| 21 | + signatures?: HexLike[] | null; |
| 22 | +}; |
| 23 | + |
| 24 | +/** |
| 25 | + * A class representing multisig information, holding information ingredients and containing utilities. |
| 26 | + * @public |
| 27 | + */ |
| 28 | +@codec({ |
| 29 | + encode: (encodable: MultisigCkbWitness) => { |
| 30 | + const { publicKeyHashes, threshold, mustMatch, signatures } = |
| 31 | + MultisigCkbWitness.from(encodable); |
| 32 | + |
| 33 | + if ( |
| 34 | + signatures.some((s) => s.length !== SECP256K1_SIGNATURE_LENGTH * 2 + 2) |
| 35 | + ) { |
| 36 | + throw Error("MultisigCkbWitness: invalid signature length"); |
| 37 | + } |
| 38 | + if ( |
| 39 | + publicKeyHashes.some((s) => s.length !== HASH_CKB_SHORT_LENGTH * 2 + 2) |
| 40 | + ) { |
| 41 | + throw Error("MultisigCkbWitness: invalid public key hash length"); |
| 42 | + } |
| 43 | + |
| 44 | + return bytesConcat( |
| 45 | + "0x00", |
| 46 | + numToBytes(mustMatch ?? 0), |
| 47 | + numToBytes(threshold), |
| 48 | + numToBytes(publicKeyHashes.length), |
| 49 | + ...publicKeyHashes, |
| 50 | + ...signatures, |
| 51 | + ); |
| 52 | + }, |
| 53 | + decode: (raw: Bytes) => { |
| 54 | + const [ |
| 55 | + _reserved, |
| 56 | + mustMatch, |
| 57 | + threshold, |
| 58 | + publicKeyHashesLength, |
| 59 | + ...rawKeyAndSignatures |
| 60 | + ] = raw; |
| 61 | + |
| 62 | + if ( |
| 63 | + rawKeyAndSignatures.length < |
| 64 | + publicKeyHashesLength * HASH_CKB_SHORT_LENGTH |
| 65 | + ) { |
| 66 | + throw Error("MultisigCkbWitness: invalid public key hashes length"); |
| 67 | + } |
| 68 | + |
| 69 | + const signatures = rawKeyAndSignatures.slice( |
| 70 | + publicKeyHashesLength * HASH_CKB_SHORT_LENGTH, |
| 71 | + ); |
| 72 | + |
| 73 | + return MultisigCkbWitness.from({ |
| 74 | + publicKeyHashes: Array.from(new Array(publicKeyHashesLength), (_, i) => |
| 75 | + hexFrom( |
| 76 | + rawKeyAndSignatures.slice( |
| 77 | + i * HASH_CKB_SHORT_LENGTH, |
| 78 | + (i + 1) * HASH_CKB_SHORT_LENGTH, |
| 79 | + ), |
| 80 | + ), |
| 81 | + ), |
| 82 | + threshold: numFrom(threshold), |
| 83 | + mustMatch: numFrom(mustMatch), |
| 84 | + signatures: Array.from( |
| 85 | + new Array(Math.floor(signatures.length / SECP256K1_SIGNATURE_LENGTH)), |
| 86 | + (_, i) => |
| 87 | + hexFrom( |
| 88 | + signatures.slice( |
| 89 | + i * SECP256K1_SIGNATURE_LENGTH, |
| 90 | + (i + 1) * SECP256K1_SIGNATURE_LENGTH, |
| 91 | + ), |
| 92 | + ), |
| 93 | + ), |
| 94 | + }); |
| 95 | + }, |
| 96 | +}) |
| 97 | +export class MultisigCkbWitness extends Entity.Base< |
| 98 | + MultisigCkbWitnessLike, |
| 99 | + MultisigCkbWitness |
| 100 | +>() { |
| 101 | + /** |
| 102 | + * @param publicKeyHashes - The public key hashes. |
| 103 | + * @param threshold - The threshold. |
| 104 | + * @param mustMatch - The number of signatures that must match. |
| 105 | + * @param signatures - The signatures. |
| 106 | + */ |
| 107 | + constructor( |
| 108 | + public publicKeyHashes: Hex[], |
| 109 | + public threshold: number, |
| 110 | + public mustMatch: number, |
| 111 | + public signatures: Hex[], |
| 112 | + ) { |
| 113 | + super(); |
| 114 | + |
| 115 | + const keysLength = publicKeyHashes.length; |
| 116 | + |
| 117 | + if (threshold <= 0 || threshold > keysLength) { |
| 118 | + throw new Error( |
| 119 | + "threshold should be in range from 1 to public keys length", |
| 120 | + ); |
| 121 | + } |
| 122 | + if (mustMatch < 0 || mustMatch > Math.min(keysLength, threshold)) { |
| 123 | + throw new Error( |
| 124 | + "mustMatch should be in range from 0 to min(public keys length, threshold)", |
| 125 | + ); |
| 126 | + } |
| 127 | + if (keysLength > 255) { |
| 128 | + throw new Error("public keys length should be less than 256"); |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + /** |
| 133 | + * Create a MultisigCkbWitness from a MultisigCkbWitnessLike. |
| 134 | + * |
| 135 | + * @param witness - The witness like object. |
| 136 | + * @returns The MultisigCkbWitness. |
| 137 | + */ |
| 138 | + static from(witness: MultisigCkbWitnessLike): MultisigCkbWitness { |
| 139 | + const publicKeyHashes = (() => { |
| 140 | + if (witness.publicKeyHashes) { |
| 141 | + return witness.publicKeyHashes; |
| 142 | + } |
| 143 | + return witness.publicKeys.map((k) => hashCkb(k).slice(0, 42)); |
| 144 | + })(); |
| 145 | + |
| 146 | + return new MultisigCkbWitness( |
| 147 | + publicKeyHashes.map(hexFrom), |
| 148 | + Number(numFrom(witness.threshold)), |
| 149 | + Number(numFrom(witness.mustMatch ?? 0)), |
| 150 | + witness.signatures?.map(hexFrom) ?? [], |
| 151 | + ); |
| 152 | + } |
| 153 | + |
| 154 | + /** |
| 155 | + * Get the script args of the multisig script. |
| 156 | + * |
| 157 | + * @param since - The since value. |
| 158 | + * @returns The script args. |
| 159 | + */ |
| 160 | + scriptArgs(since?: SinceLike | null): Bytes { |
| 161 | + const hash = hashCkb(this.toBytes()).slice(0, 42); |
| 162 | + |
| 163 | + if (since != null) { |
| 164 | + return bytesConcat(hash, Since.from(since).toBytes()); |
| 165 | + } |
| 166 | + |
| 167 | + return bytesFrom(hash); |
| 168 | + } |
| 169 | + |
| 170 | + /** |
| 171 | + * Check if the multisig info is equal to another. |
| 172 | + * |
| 173 | + * @param otherLike - The other multisig info. |
| 174 | + * @returns True if the multisig info is equal, false otherwise. |
| 175 | + */ |
| 176 | + eqInfo(otherLike: MultisigCkbWitnessLike): boolean { |
| 177 | + const other = MultisigCkbWitness.from(otherLike); |
| 178 | + return ( |
| 179 | + this.publicKeyHashes.length === other.publicKeyHashes.length && |
| 180 | + this.publicKeyHashes.every((h, i) => h === other.publicKeyHashes[i]) && |
| 181 | + this.threshold === other.threshold && |
| 182 | + this.mustMatch === other.mustMatch |
| 183 | + ); |
| 184 | + } |
| 185 | +} |
0 commit comments