diff --git a/packages/ur-registry-stellar/README.md b/packages/ur-registry-stellar/README.md new file mode 100644 index 0000000..bc81f66 --- /dev/null +++ b/packages/ur-registry-stellar/README.md @@ -0,0 +1,15 @@ +# BC-UR-Registry-Stellar + +This repository is the Stellar extension of [bc-ur-registry](https://github.com/KeystoneHQ/ur-registry) + +## Installing + +To install, run: + +```bash +yarn add @keystonehq/bc-ur-registry-stellar +``` + +```bash +npm install --save @keystonehq/bc-ur-registry-stellar +``` diff --git a/packages/ur-registry-stellar/__tests__/StellarSignRequest.test.ts b/packages/ur-registry-stellar/__tests__/StellarSignRequest.test.ts new file mode 100644 index 0000000..994c102 --- /dev/null +++ b/packages/ur-registry-stellar/__tests__/StellarSignRequest.test.ts @@ -0,0 +1,76 @@ +// @ts-nocheck + +import { StellarSignRequest, SignType } from "../src"; +import { CryptoKeypath, PathComponent } from "../src"; +import * as uuid from "uuid"; + +describe("stellar-sign-request", () => { + it("test should generate stellar-sign-reqeust", () => { + const stellarData = Buffer.from( + "01000103c8d842a2f17fd7aab608ce2ea535a6e958dffa20caf669b347b911c4171965530f957620b228bae2b94c82ddd4c093983a67365555b737ec7ddc1117e61c72e0000000000000000000000000000000000000000000000000000000000000000010295cc2f1f39f3604718496ea00676d6a72ec66ad09d926e3ece34f565f18d201020200010c0200000000e1f50500000000", + "hex" + ); + + const signKeyPath = new CryptoKeypath( + [ + new PathComponent({ index: 44, hardened: true }), + new PathComponent({ index: 501, hardened: true }), + new PathComponent({ index: 0, hardened: true }), + new PathComponent({ index: 0, hardened: true }), + ], + Buffer.from("12345678", "hex") + ); + + const stellarRequestId = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"; + const idBuffer = uuid.parse(stellarRequestId) as Uint8Array; + + const stellarSignRequest = new StellarSignRequest({ + signData: stellarData, + derivationPath: signKeyPath, + requestId: Buffer.from(idBuffer), + origin: "solflare", + signType: SignType.Transaction, + }); + + const cborHex = stellarSignRequest.toCBOR().toString("hex"); + const ur = stellarSignRequest.toUREncoder(1000).nextPart(); + expect(ur).toBe( + "ur:stellar-sign-request/onadtpdagdndcawmgtfrkigrpmndutdnbtkgfssbjnaohdmtadaeadaxsptpfwoewnlbtspkrpaytodmonecolwlhdurzscxsgyninqdflrhbysschcfihgubsmdkocxprderdvorhgslfuttyrtmumkftioengogorlemwpkiuobychvacejpvtaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaebedthhsawnwfneenaajslrmtwdaeiojnimjpwpiypmastadsvlwpvlgwhfhecstdadaoaoaeadbnaoaeaeaeaevyykahaeaeaeaeaxtaaddyoeadlocsdwykcfadykykaeykaeykaocybgeehfksahisjkjljziyjzhsjpihamadkkgseofg" + ); + const stellarSignRequestDecoded = StellarSignRequest.fromCBOR( + Buffer.from(cborHex, "hex") + ); + expect(uuid.stringify(stellarSignRequest.getRequestId())).toBe( + stellarRequestId + ); + expect(stellarSignRequest.getOrigin()).toBe("solflare"); + expect(stellarSignRequestDecoded.getSignData().toString("hex")).toEqual( + "01000103c8d842a2f17fd7aab608ce2ea535a6e958dffa20caf669b347b911c4171965530f957620b228bae2b94c82ddd4c093983a67365555b737ec7ddc1117e61c72e0000000000000000000000000000000000000000000000000000000000000000010295cc2f1f39f3604718496ea00676d6a72ec66ad09d926e3ece34f565f18d201020200010c0200000000e1f50500000000" + ); + expect(stellarSignRequestDecoded.getSignType()).toBe(SignType.Transaction); + }); + + it("should construct an StellarSignRequest object from string", () => { + const hdPath = "M/44'/501'/0'/0'"; + const xfp = "12345678"; + const stellarData = Buffer.from( + "01000103c8d842a2f17fd7aab608ce2ea535a6e958dffa20caf669b347b911c4171965530f957620b228bae2b94c82ddd4c093983a67365555b737ec7ddc1117e61c72e0000000000000000000000000000000000000000000000000000000000000000010295cc2f1f39f3604718496ea00676d6a72ec66ad09d926e3ece34f565f18d201020200010c0200000000e1f50500000000", + "hex" + ); + const requestID = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"; + + const request = StellarSignRequest.constructStellarRequest( + stellarData, + hdPath, + xfp, + SignType.Message, + requestID, + undefined, + "solflare" + ); + const ur = request.toUREncoder(1000).nextPart(); + expect(ur).toBe( + "ur:stellar-sign-request/onadtpdagdndcawmgtfrkigrpmndutdnbtkgfssbjnaohdmtadaeadaxsptpfwoewnlbtspkrpaytodmonecolwlhdurzscxsgyninqdflrhbysschcfihgubsmdkocxprderdvorhgslfuttyrtmumkftioengogorlemwpkiuobychvacejpvtaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaebedthhsawnwfneenaajslrmtwdaeiojnimjpwpiypmastadsvlwpvlgwhfhecstdadaoaoaeadbnaoaeaeaeaevyykahaeaeaeaeaxtaaddyoeadlocsdwykcfadykykaeykaeykaocybgeehfksahisjkjljziyjzhsjpihamaovtfeidzt" + ); + }); +}); diff --git a/packages/ur-registry-stellar/__tests__/StellarSignature.test.ts b/packages/ur-registry-stellar/__tests__/StellarSignature.test.ts new file mode 100644 index 0000000..7c596cb --- /dev/null +++ b/packages/ur-registry-stellar/__tests__/StellarSignature.test.ts @@ -0,0 +1,35 @@ +// @ts-nocheck + +import { StellarSignature } from "../src"; +import * as uuid from "uuid"; + +describe("stellar-sign-request", () => { + it("test should generate stellar-signature", () => { + const signature = Buffer.from( + "d4f0a7bcd95bba1fbb1051885054730e3f47064288575aacc102fbbf6a9a14daa066991e360d3e3406c20c00a40973eff37c7d641e5b351ec4a99bfe86f335f7", + "hex" + ); + const stellarRequestId = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"; + const idBuffer = uuid.parse(stellarRequestId) as Uint8Array; + + const stellarSignature = new StellarSignature( + signature, + Buffer.from(idBuffer) + ); + + const cborHex = stellarSignature.toCBOR().toString("hex"); + const ur = stellarSignature.toUREncoder(1000).nextPart(); + expect(ur).toBe( + "ur:stellar-signature/oeadtpdagdndcawmgtfrkigrpmndutdnbtkgfssbjnaohdfztywtosrftahprdctrkbegylogdghjkbafhflamfwlohghtpsseaozorsimnybbtnnbiynlckenbtfmeeamsabnaeoxasjkwswfkekiieckhpecckssptndzelnwfecyldrcyhkws" + ); + const StellarSignatureDecoded = StellarSignature.fromCBOR( + Buffer.from(cborHex, "hex") + ); + expect(uuid.stringify(StellarSignatureDecoded.getRequestId())).toBe( + stellarRequestId + ); + expect(StellarSignatureDecoded.getSignature().toString("hex")).toEqual( + "d4f0a7bcd95bba1fbb1051885054730e3f47064288575aacc102fbbf6a9a14daa066991e360d3e3406c20c00a40973eff37c7d641e5b351ec4a99bfe86f335f7" + ); + }); +}); diff --git a/packages/ur-registry-stellar/babel.config.js b/packages/ur-registry-stellar/babel.config.js new file mode 100644 index 0000000..dd242dc --- /dev/null +++ b/packages/ur-registry-stellar/babel.config.js @@ -0,0 +1,6 @@ +module.exports = { + presets: [ + ["@babel/preset-env", { targets: { node: "current" } }], + "@babel/preset-typescript", + ], +}; diff --git a/packages/ur-registry-stellar/package.json b/packages/ur-registry-stellar/package.json new file mode 100644 index 0000000..e751489 --- /dev/null +++ b/packages/ur-registry-stellar/package.json @@ -0,0 +1,38 @@ +{ + "name": "@keystonehq/bc-ur-registry-stellar", + "version": "0.0.1", + "description": "bc-ur-registry extension for Stellar", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "directories": { + "lib": "src", + "test": "__tests__" + }, + "files": [ + "src", + "dist" + ], + "scripts": { + "clean": "rm -rf ./dist", + "start": "tsdx watch", + "build": "tsdx build", + "test": "jest --passWithNoTests" + }, + "publishConfig": { + "access": "public" + }, + "author": "xi@keyst.com", + "license": "ISC", + "dependencies": { + "@keystonehq/bc-ur-registry": "^0.5.4", + "bs58check": "^2.1.2", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@babel/preset-typescript": "^7.15.0", + "@types/uuid": "^8.3.1", + "tsdx": "^0.14.1", + "typescript": "^4.6.2" + }, + "gitHead": "83d8e223d29e5cc71dccc963388d65a87c894636" +} \ No newline at end of file diff --git a/packages/ur-registry-stellar/src/RegistryType.ts b/packages/ur-registry-stellar/src/RegistryType.ts new file mode 100644 index 0000000..fd8438d --- /dev/null +++ b/packages/ur-registry-stellar/src/RegistryType.ts @@ -0,0 +1,6 @@ +import { RegistryType } from "@keystonehq/bc-ur-registry"; + +export const ExtendedRegistryTypes = { + STELLAR_SIGN_REQUEST: new RegistryType("stellar-sign-request", 8201), + STELLAR_SIGNATURE: new RegistryType("stellar-signature", 8202), +}; diff --git a/packages/ur-registry-stellar/src/StellarSignRequest.ts b/packages/ur-registry-stellar/src/StellarSignRequest.ts new file mode 100644 index 0000000..2101e0e --- /dev/null +++ b/packages/ur-registry-stellar/src/StellarSignRequest.ts @@ -0,0 +1,151 @@ +import { + CryptoKeypath, + extend, + DataItem, + PathComponent, + RegistryItem, + DataItemMap, +} from "@keystonehq/bc-ur-registry"; +import { ExtendedRegistryTypes } from "./RegistryType"; +import * as uuid from "uuid"; + +const { decodeToDataItem, RegistryTypes } = extend; + +export enum SignType { + Transaction = 1, + Message = 2, +} + +enum Keys { + requestId = 1, + signData, + derivationPath, + address, + origin, + signType, +} + +type signRequestProps = { + requestId?: Buffer; + signData: Buffer; + derivationPath: CryptoKeypath; + address?: Buffer; + origin?: string; + signType: SignType; +}; + +export class StellarSignRequest extends RegistryItem { + private requestId?: Buffer; + private signData: Buffer; + private derivationPath: CryptoKeypath; + private address?: Buffer; + private origin?: string; + private signType: SignType; + + getRegistryType = () => ExtendedRegistryTypes.STELLAR_SIGN_REQUEST; + + constructor(args: signRequestProps) { + super(); + this.requestId = args.requestId; + this.signData = args.signData; + this.derivationPath = args.derivationPath; + this.address = args.address; + this.origin = args.origin; + this.signType = args.signType; + } + + public getRequestId = () => this.requestId; + public getSignData = () => this.signData; + public getDerivationPath = () => this.derivationPath.getPath(); + public getSignRequestAddress = () => this.address; + public getOrigin = () => this.origin; + public getSignType = () => this.signType; + + public toDataItem = () => { + const map: DataItemMap = {}; + if (this.requestId) { + map[Keys.requestId] = new DataItem( + this.requestId, + RegistryTypes.UUID.getTag() + ); + } + if (this.address) { + map[Keys.address] = this.address; + } + + if (this.origin) { + map[Keys.origin] = this.origin; + } + + map[Keys.signData] = this.signData; + map[Keys.signType] = this.signType; + + const keyPath = this.derivationPath.toDataItem(); + keyPath.setTag(this.derivationPath.getRegistryType().getTag()); + map[Keys.derivationPath] = keyPath; + + return new DataItem(map); + }; + + public static fromDataItem = (dataItem: DataItem) => { + const map = dataItem.getData(); + const signData = map[Keys.signData]; + const derivationPath = CryptoKeypath.fromDataItem(map[Keys.derivationPath]); + const address = map[Keys.address] ? map[Keys.address] : undefined; + const requestId = map[Keys.requestId] + ? map[Keys.requestId].getData() + : undefined; + const origin = map[Keys.origin] ? map[Keys.origin] : undefined; + const signType = map[Keys.signType]; + + return new StellarSignRequest({ + requestId, + signData, + derivationPath, + address, + origin, + signType, + }); + }; + + public static fromCBOR = (_cborPayload: Buffer) => { + const dataItem = decodeToDataItem(_cborPayload); + return StellarSignRequest.fromDataItem(dataItem); + }; + + public static constructStellarRequest( + signData: Buffer, + hdPath: string, + xfp: string, + signType: SignType, + uuidString?: string, + address?: string, + origin?: string + ) { + const paths = hdPath.replace(/[m|M]\//, "").split("/"); + const hdpathObject = new CryptoKeypath( + paths.map((path) => { + const index = parseInt(path.replace("'", "")); + let isHardened = false; + if (path.endsWith("'")) { + isHardened = true; + } + return new PathComponent({ index, hardened: isHardened }); + }), + Buffer.from(xfp, "hex") + ); + + return new StellarSignRequest({ + requestId: uuidString + ? Buffer.from(uuid.parse(uuidString) as Uint8Array) + : undefined, + signData, + derivationPath: hdpathObject, + address: address + ? Buffer.from(address.replace("0x", ""), "hex") + : undefined, + origin: origin || undefined, + signType, + }); + } +} diff --git a/packages/ur-registry-stellar/src/StellarSignature.ts b/packages/ur-registry-stellar/src/StellarSignature.ts new file mode 100644 index 0000000..aa69fd0 --- /dev/null +++ b/packages/ur-registry-stellar/src/StellarSignature.ts @@ -0,0 +1,57 @@ +import { + extend, + DataItem, + RegistryItem, + DataItemMap, +} from "@keystonehq/bc-ur-registry"; +import { ExtendedRegistryTypes } from "./RegistryType"; + +const { RegistryTypes, decodeToDataItem } = extend; + +enum Keys { + requestId = 1, + signature, +} + +export class StellarSignature extends RegistryItem { + private requestId?: Buffer; + private signature: Buffer; + + getRegistryType = () => ExtendedRegistryTypes.STELLAR_SIGNATURE; + + constructor(signature: Buffer, requestId?: Buffer) { + super(); + this.signature = signature; + this.requestId = requestId; + } + + public getRequestId = () => this.requestId; + public getSignature = () => this.signature; + + public toDataItem = () => { + const map: DataItemMap = {}; + if (this.requestId) { + map[Keys.requestId] = new DataItem( + this.requestId, + RegistryTypes.UUID.getTag() + ); + } + map[Keys.signature] = this.signature; + return new DataItem(map); + }; + + public static fromDataItem = (dataItem: DataItem) => { + const map = dataItem.getData(); + const signature = map[Keys.signature]; + const requestId = map[Keys.requestId] + ? map[Keys.requestId].getData() + : undefined; + + return new StellarSignature(signature, requestId); + }; + + public static fromCBOR = (_cborPayload: Buffer) => { + const dataItem = decodeToDataItem(_cborPayload); + return StellarSignature.fromDataItem(dataItem); + }; +} diff --git a/packages/ur-registry-stellar/src/index.ts b/packages/ur-registry-stellar/src/index.ts new file mode 100644 index 0000000..c758d28 --- /dev/null +++ b/packages/ur-registry-stellar/src/index.ts @@ -0,0 +1,12 @@ +import { patchTags } from "@keystonehq/bc-ur-registry"; +import { ExtendedRegistryTypes } from "./RegistryType"; +export * from "@keystonehq/bc-ur-registry"; + +patchTags( + Object.values(ExtendedRegistryTypes) + .filter((rt) => !!rt.getTag()) + .map((rt) => rt.getTag()) as number[] +); + +export { StellarSignRequest, SignType } from "./StellarSignRequest"; +export { StellarSignature } from "./StellarSignature"; diff --git a/packages/ur-registry-stellar/tsconfig.json b/packages/ur-registry-stellar/tsconfig.json new file mode 100644 index 0000000..b051e97 --- /dev/null +++ b/packages/ur-registry-stellar/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src"], + "compilerOptions": { + "noImplicitAny": true, + "strictNullChecks": true, + "rootDir": "./", + "baseUrl": "./", + "paths": { + "*": ["src/*", "node_modules/*"] + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5392f59..0b35b2f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,8 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false neverBuiltDependencies: - node-hid @@ -193,7 +197,7 @@ importers: version: 8.1.0 '@keystonehq/bc-ur-registry-eth': specifier: ^0.19.1 - version: link:../ur-registry-eth + version: 0.19.1 hdkey: specifier: ^2.0.1 version: 2.1.0 @@ -252,7 +256,7 @@ importers: version: link:../base-eth-keyring '@keystonehq/bc-ur-registry-eth': specifier: ^0.19.1 - version: link:../ur-registry-eth + version: 0.19.1 '@keystonehq/sdk': specifier: ^0.19.2 version: 0.19.2(react-dom@16.14.0)(react@16.14.0) @@ -391,7 +395,7 @@ importers: version: link:../base-eth-keyring '@keystonehq/bc-ur-registry-eth': specifier: ^0.19.1 - version: link:../ur-registry-eth + version: 0.19.1 '@metamask/obs-store': specifier: ^9.0.0 version: 9.0.0 @@ -844,6 +848,31 @@ importers: specifier: ^4.6.2 version: 4.9.5 + packages/ur-registry-stellar: + dependencies: + '@keystonehq/bc-ur-registry': + specifier: ^0.5.4 + version: 0.5.5 + bs58check: + specifier: ^2.1.2 + version: 2.1.2 + uuid: + specifier: ^8.3.2 + version: 8.3.2 + devDependencies: + '@babel/preset-typescript': + specifier: ^7.15.0 + version: 7.24.1(@babel/core@7.24.5) + '@types/uuid': + specifier: ^8.3.1 + version: 8.3.4 + tsdx: + specifier: ^0.14.1 + version: 0.14.1 + typescript: + specifier: ^4.6.2 + version: 4.9.5 + packages/ur-registry-sui: dependencies: '@keystonehq/bc-ur-registry': @@ -2992,6 +3021,15 @@ packages: uuid: 8.3.2 dev: false + /@keystonehq/bc-ur-registry-eth@0.19.1: + resolution: {integrity: sha512-5+skb1zsmMEIGZCbk+4KssZTpLMTriaFlt+Lc6pZLmxexXrX8a/9aHoho3asOqf7GeXXqkB9YKs8i8TN/hbaHA==} + dependencies: + '@ethereumjs/util': 8.1.0 + '@keystonehq/bc-ur-registry': 0.6.4 + hdkey: 2.1.0 + uuid: 8.3.2 + dev: false + /@keystonehq/bc-ur-registry-eth@0.6.14: resolution: {integrity: sha512-Zr0VAUJuzz5zfH2263AucdWPUYuclpd93Pmi/VzbML72sQLv8l83kQWmQpI+7639uV5dHcOj6JnD8FhCPYPRFQ==} dependencies: @@ -15061,14 +15099,10 @@ packages: dev: true github.com/ethereumjs/ethereumjs-abi/ee3994657fa7a427238e6ba92a84d0b529bbcde0: - resolution: {commit: ee3994657fa7a427238e6ba92a84d0b529bbcde0, repo: git+ssh://git@github.com/ethereumjs/ethereumjs-abi.git, type: git} + resolution: {tarball: https://codeload.github.com/ethereumjs/ethereumjs-abi/tar.gz/ee3994657fa7a427238e6ba92a84d0b529bbcde0} name: ethereumjs-abi version: 0.6.8 dependencies: bn.js: 4.12.0 ethereumjs-util: 6.2.1 dev: false - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false