2023-09-07 22:40:27 +00:00
|
|
|
import Multibase from "#multibase.js";
|
|
|
|
import { Multihash } from "#multihash.js";
|
|
|
|
import { CID_TYPES, REGISTRY_TYPES } from "#constants.js";
|
|
|
|
import { decodeEndian, encodeEndian } from "#util.js";
|
|
|
|
import { concatBytes, equalBytes } from "@noble/curves/abstract/utils";
|
2023-09-07 22:51:40 +00:00
|
|
|
import { hexToBytes } from "@noble/hashes/utils";
|
2023-09-07 22:40:27 +00:00
|
|
|
|
|
|
|
export default class CID extends Multibase {
|
|
|
|
type: number;
|
|
|
|
hash: Multihash;
|
|
|
|
size?: number;
|
|
|
|
|
|
|
|
constructor(type: number, hash: Multihash, size?: number) {
|
|
|
|
super();
|
|
|
|
this.type = type;
|
|
|
|
this.hash = hash;
|
|
|
|
this.size = size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static decode(cid: string): CID {
|
|
|
|
const decodedBytes = Multibase.decodeString(cid);
|
|
|
|
return CID._init(decodedBytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
static fromBytes(bytes: Uint8Array): CID {
|
|
|
|
return CID._init(bytes);
|
|
|
|
}
|
|
|
|
|
2023-09-07 22:51:40 +00:00
|
|
|
static fromHash(
|
|
|
|
bytes: string | Uint8Array,
|
|
|
|
size: number,
|
|
|
|
type = CID_TYPES.RAW,
|
|
|
|
): CID {
|
|
|
|
if (typeof bytes === "string") {
|
|
|
|
bytes = hexToBytes(bytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Object.values(CID_TYPES).includes(type)) {
|
|
|
|
throw new Error(`invalid cid type ${type}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new CID(type, new Multihash(bytes), size);
|
|
|
|
}
|
|
|
|
|
2023-09-07 22:40:27 +00:00
|
|
|
private static _init(bytes: Uint8Array): CID {
|
|
|
|
const type = bytes[0];
|
|
|
|
if (type === CID_TYPES.BRIDGE) {
|
|
|
|
return new CID(type, new Multihash(bytes));
|
|
|
|
}
|
|
|
|
|
|
|
|
const hash = new Multihash(bytes.subarray(1, 34));
|
|
|
|
let size: number | undefined = undefined;
|
|
|
|
const sizeBytes = bytes.subarray(34);
|
|
|
|
if (sizeBytes.length > 0) {
|
|
|
|
size = decodeEndian(sizeBytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Object.values(CID_TYPES).includes(type)) {
|
|
|
|
throw new Error(`invalid cid type ${type}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new CID(type, hash, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
copyWith({ size, type }: { type?: number; size?: number }): CID {
|
|
|
|
type = type || this.type;
|
|
|
|
|
|
|
|
if (!Object.values(CID_TYPES).includes(type)) {
|
|
|
|
throw new Error(`invalid cid type ${type}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new CID(type, this.hash, size || this.size);
|
|
|
|
}
|
|
|
|
|
|
|
|
toBytes(): Uint8Array {
|
|
|
|
if (this.type === CID_TYPES.BRIDGE) {
|
|
|
|
return this.hash.fullBytes;
|
|
|
|
} else if (this.type === CID_TYPES.RAW) {
|
|
|
|
let sizeBytes = encodeEndian(this.size as number, 8);
|
|
|
|
|
|
|
|
while (sizeBytes.length > 0 && sizeBytes[sizeBytes.length - 1] === 0) {
|
|
|
|
sizeBytes = sizeBytes.slice(0, -1);
|
|
|
|
}
|
|
|
|
if (sizeBytes.length === 0) {
|
|
|
|
sizeBytes = new Uint8Array(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return concatBytes(
|
|
|
|
this._getPrefixBytes(),
|
|
|
|
this.hash.fullBytes,
|
|
|
|
sizeBytes,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return concatBytes(this._getPrefixBytes(), this.hash.fullBytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
private _getPrefixBytes(): Uint8Array {
|
|
|
|
return Uint8Array.from([this.type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
toRegistryEntry(): Uint8Array {
|
|
|
|
return concatBytes(Uint8Array.from([REGISTRY_TYPES.CID]), this.toBytes());
|
|
|
|
}
|
|
|
|
|
|
|
|
toRegistryCID(): Uint8Array {
|
|
|
|
return this.copyWith({ type: CID_TYPES.RESOLVER }).toBytes();
|
|
|
|
}
|
|
|
|
|
|
|
|
toString(): string {
|
|
|
|
return this.type === CID_TYPES.BRIDGE
|
|
|
|
? Buffer.from(this.hash.fullBytes).toString("utf8")
|
|
|
|
: this.toBase58();
|
|
|
|
}
|
|
|
|
|
|
|
|
equals(other: CID): boolean {
|
|
|
|
return equalBytes(this.toBytes(), other.toBytes());
|
|
|
|
}
|
|
|
|
|
|
|
|
hashCode(): number {
|
|
|
|
const fullBytes = this.toBytes();
|
|
|
|
return (
|
|
|
|
fullBytes[0] +
|
|
|
|
fullBytes[1] * 256 +
|
|
|
|
fullBytes[2] * 256 * 256 +
|
|
|
|
fullBytes[3] * 256 * 256 * 256
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|