From 72f4cbe6c8aaedd09a94e072a6d19887f5d25da4 Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Fri, 1 Nov 2024 06:50:40 -0400 Subject: [PATCH 1/2] feat: add base64 getters to udi class --- src/utils/Udi.ts | 177 ++++++++++++++++++++--------------------------- 1 file changed, 76 insertions(+), 101 deletions(-) diff --git a/src/utils/Udi.ts b/src/utils/Udi.ts index a8871cc..6544c9c 100644 --- a/src/utils/Udi.ts +++ b/src/utils/Udi.ts @@ -1,108 +1,102 @@ -import * as urns from "urns"; -import { createHash } from "crypto"; -import { encode as base32Encode, decode as base32Decode } from "hi-base32"; - +import * as urns from 'urns'; +import { createHash } from 'crypto'; +import { encode as base32Encode, decode as base32Decode } from 'hi-base32'; + +// +// This class encapsulates the concept of a Universal Data Identifier (UDI), which is a +// standardized way to identify resources across the distributed DIG mesh network. +// The UDI format: urn:dig:chainName:storeId:rootHash/resourceKey +// This allows unique resource identification across the DIG network. +// class Udi { readonly chainName: string; - private readonly _storeIdHex: string; - private readonly _rootHashHex: string | null; + private readonly _storeId: Buffer; + private readonly _rootHash: Buffer | null; readonly resourceKey: string | null; static readonly nid: string = "dig"; static readonly namespace: string = `urn:${Udi.nid}`; constructor( chainName: string, - storeId: string, - rootHash: string | null = null, + storeId: string | Buffer, + rootHash: string | Buffer | null = null, resourceKey: string | null = null ) { if (!storeId) { throw new Error("storeId cannot be empty"); } this.chainName = chainName || "chia"; - this._storeIdHex = Udi.verifyAndFormatHex(storeId); - this._rootHashHex = rootHash ? Udi.verifyAndFormatHex(rootHash) : null; + this._storeId = Udi.convertToBuffer(storeId); + this._rootHash = rootHash ? Udi.convertToBuffer(rootHash) : null; this.resourceKey = resourceKey; } - static verifyAndFormatHex(input: string): string { - if (!/^[a-fA-F0-9]{64}$/.test(input)) { - throw new Error("Input must be a 64-character hex string."); + static convertToBuffer(input: string | Buffer): Buffer { + if (Buffer.isBuffer(input)) { + return input; + } + + if (Udi.isHex(input)) { + return Buffer.from(input, 'hex'); + } + + if (Udi.isBase32(input)) { + return Buffer.from(base32Decode(input.toUpperCase(), false)); // Decode with uppercase } - return input; + + throw new Error("Invalid input encoding. Must be 32-byte hex or Base32 string."); + } + + static isHex(input: string): boolean { + return /^[a-fA-F0-9]{64}$/.test(input); + } + + static isBase32(input: string): boolean { + return /^[A-Z2-7]{52}$/.test(input.toUpperCase()); + } + + withRootHash(rootHash: string | Buffer | null): Udi { + return new Udi(this.chainName, this._storeId, rootHash, this.resourceKey); + } + + withResourceKey(resourceKey: string | null): Udi { + return new Udi(this.chainName, this._storeId, this._rootHash, resourceKey); } static fromUrn(urn: string): Udi { const parsedUrn = urns.parseURN(urn); - if (parsedUrn.nid !== Udi.nid) { + if (parsedUrn.nid.toLowerCase() !== Udi.nid) { throw new Error(`Invalid nid: ${parsedUrn.nid}`); } - const parts = parsedUrn.nss.split(":"); + const parts = parsedUrn.nss.split(':'); if (parts.length < 2) { throw new Error(`Invalid UDI format: ${parsedUrn.nss}`); } const chainName = parts[0]; - const storeIdHex = Udi.convertToHex(parts[1].split("/")[0]); + const storeId = parts[1].split('/')[0]; - let rootHashHex: string | null = null; + let rootHash: string | null = null; if (parts.length > 2) { - rootHashHex = Udi.convertToHex(parts[2].split("/")[0]); + rootHash = parts[2].split('/')[0]; } - const pathParts = parsedUrn.nss.split("/"); + const pathParts = parsedUrn.nss.split('/'); let resourceKey: string | null = null; if (pathParts.length > 1) { - resourceKey = pathParts.slice(1).join("/"); + resourceKey = pathParts.slice(1).join('/'); } - return new Udi(chainName, storeIdHex, rootHashHex, resourceKey); + return new Udi(chainName, storeId, rootHash, resourceKey); } - static convertToHex(input: string): string { - // Attempt hex conversion first - if (/^[a-fA-F0-9]{64}$/.test(input)) return input; - - // Convert from Base32 - try { - const paddedInput = Udi.addBase32Padding(input); - const buffer = Buffer.from(base32Decode(paddedInput, false)); - return buffer.toString("hex"); - } catch (e) { - console.warn("Base32 decoding failed, trying Base64 encoding..."); - } - - // Convert from Base64 - try { - const standardBase64 = Udi.addBase64Padding(Udi.toStandardBase64(input)); - const buffer = Buffer.from(standardBase64, "base64"); - return buffer.toString("hex"); - } catch (e) { - throw new Error("Invalid input encoding. Must be 32-byte hex, Base32, or Base64 string."); - } - } - - static addBase32Padding(input: string): string { - const paddingNeeded = (8 - (input.length % 8)) % 8; - return input + "=".repeat(paddingNeeded); - } - - static toStandardBase64(base64UrlSafe: string): string { - return base64UrlSafe.replace(/-/g, "+").replace(/_/g, "/"); - } - - static addBase64Padding(base64: string): string { - const paddingNeeded = (4 - (base64.length % 4)) % 4; - return base64 + "=".repeat(paddingNeeded); - } - - toUrn(encoding: "hex" | "base32" | "base64" = "hex"): string { - const storeIdStr = this.formatBufferAsEncoding(this._storeIdHex, encoding); + toUrn(encoding: 'hex' | 'base32' | 'base64' = 'hex'): string { + const storeIdStr = this.bufferToString(this._storeId, encoding); let urn = `${Udi.namespace}:${this.chainName}:${storeIdStr}`; - if (this._rootHashHex) { - const rootHashStr = this.formatBufferAsEncoding(this._rootHashHex, encoding); + if (this._rootHash) { + const rootHashStr = this.bufferToString(this._rootHash, encoding); urn += `:${rootHashStr}`; } @@ -113,29 +107,24 @@ class Udi { return urn; } - private formatBufferAsEncoding(hexString: string, encoding: "hex" | "base32" | "base64"): string { - const buffer = Buffer.from(hexString, "hex"); - if (encoding === "hex") { - return hexString; - } else if (encoding === "base32") { - return base32Encode(buffer).replace(/=+$/, ""); - } else if (encoding === "base64") { - return Udi.toBase64UrlSafe(buffer.toString("base64")); + bufferToString(buffer: Buffer, encoding: 'hex' | 'base32' | 'base64'): string { + switch (encoding) { + case 'hex': + return buffer.toString('hex'); + case 'base32': + return base32Encode(buffer).toUpperCase().replace(/=+$/, ''); // Convert to uppercase and remove padding + case 'base64': + return buffer.toString('base64').toLowerCase(); // Convert to lowercase + default: + throw new Error("Unsupported encoding"); } - throw new Error("Unsupported encoding type"); - } - - static toBase64UrlSafe(base64Standard: string): string { - return base64Standard.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } equals(other: Udi): boolean { return ( - this._storeIdHex === other._storeIdHex && + this._storeId.equals(other._storeId) && this.chainName === other.chainName && - (this._rootHashHex && other._rootHashHex - ? this._rootHashHex === other._rootHashHex - : this._rootHashHex === other._rootHashHex) && + (this._rootHash && other._rootHash ? this._rootHash.equals(other._rootHash) : this._rootHash === other._rootHash) && this.resourceKey === other.resourceKey ); } @@ -145,37 +134,23 @@ class Udi { } clone(): Udi { - return new Udi(this.chainName, this._storeIdHex, this._rootHashHex, this.resourceKey); + return new Udi(this.chainName, this._storeId, this._rootHash, this.resourceKey); } hashCode(): string { - const hash = createHash("sha256"); + const hash = createHash('sha256'); hash.update(this.toUrn()); - return hash.digest("hex"); + return hash.digest('hex'); } + // Getter for storeId as a hex string get storeId(): string { - return this._storeIdHex; + return this._storeId.toString('hex'); } + // Getter for rootHash as a hex string get rootHash(): string | null { - return this._rootHashHex; - } - - get storeIdBase32(): string { - return this.formatBufferAsEncoding(this._storeIdHex, "base32"); - } - - get rootHashBase32(): string | null { - return this._rootHashHex ? this.formatBufferAsEncoding(this._rootHashHex, "base32") : null; - } - - get storeIdBase64(): string { - return this.formatBufferAsEncoding(this._storeIdHex, "base64"); - } - - get rootHashBase64(): string | null { - return this._rootHashHex ? this.formatBufferAsEncoding(this._rootHashHex, "base64") : null; + return this._rootHash ? this._rootHash.toString('hex') : null; } } From 468db4312b2dc6e83aa8b2c0ea276c26be278f24 Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Fri, 1 Nov 2024 06:51:08 -0400 Subject: [PATCH 2/2] chore(release): 0.0.1-alpha.190 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3b6b44..ba35b93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [0.0.1-alpha.190](https://github.com/DIG-Network/dig-chia-sdk/compare/v0.0.1-alpha.189...v0.0.1-alpha.190) (2024-11-01) + + +### Features + +* add base64 getters to udi class ([72f4cbe](https://github.com/DIG-Network/dig-chia-sdk/commit/72f4cbe6c8aaedd09a94e072a6d19887f5d25da4)) + ### [0.0.1-alpha.189](https://github.com/DIG-Network/dig-chia-sdk/compare/v0.0.1-alpha.188...v0.0.1-alpha.189) (2024-11-01) ### [0.0.1-alpha.188](https://github.com/DIG-Network/dig-chia-sdk/compare/v0.0.1-alpha.187...v0.0.1-alpha.188) (2024-11-01) diff --git a/package-lock.json b/package-lock.json index 85ea83b..d546f58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dignetwork/dig-sdk", - "version": "0.0.1-alpha.189", + "version": "0.0.1-alpha.190", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dignetwork/dig-sdk", - "version": "0.0.1-alpha.189", + "version": "0.0.1-alpha.190", "license": "ISC", "dependencies": { "@dignetwork/datalayer-driver": "^0.1.29", diff --git a/package.json b/package.json index bac63b5..91922aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dignetwork/dig-sdk", - "version": "0.0.1-alpha.189", + "version": "0.0.1-alpha.190", "description": "", "type": "commonjs", "main": "./dist/index.js",