Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/support sign message hash #176

Merged
merged 4 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 64 additions & 62 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,64 +1,66 @@
{
"name": "keystone-airgaped-base",
"publishConfig": {
"access": "public"
},
"packageManager": "[email protected]",
"scripts": {
"clean": "./scripts/clean.sh",
"build": "pnpm -r build",
"bootstrap": "pnpm clean && pnpm install",
"lint": "eslint '*/**/*.ts' --quiet --fix",
"prettier:check": "pnpm run prettier --check",
"prettier:fix": "pnpm run prettier --write",
"prettier": "prettier '*/**/*.{js,jsx,ts,tsx}'",
"test": "pnpm -r test",
"prepack": "pnpm -r build"
},
"jest": {
"modulePathIgnorePatterns": [
"__data__"
],
"testEnvironment": "node"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
"name": "keystone-airgaped-base",
"publishConfig": {
"access": "public"
},
"packageManager": "[email protected]",
"scripts": {
"clean": "./scripts/clean.sh",
"build": "pnpm -r build",
"bootstrap": "pnpm clean && pnpm install",
"lint": "eslint '*/**/*.ts' --quiet --fix",
"prettier:check": "pnpm run prettier --check",
"prettier:fix": "pnpm run prettier --write",
"prettier": "prettier '*/**/*.{js,jsx,ts,tsx}'",
"test": "pnpm -r test",
"prepack": "pnpm -r build"
},
"jest": {
"modulePathIgnorePatterns": [
"__data__"
],
"testEnvironment": "node"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint "
],
"*.json": [
"prettier --write"
]
},
"pnpm": {
"neverBuiltDependencies": [
"node-hid"
]
},
"devDependencies": {
"@babel/cli": "^7.21.5",
"@babel/core": "^7.22.1",
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-typescript": "^7.16.7",
"@types/jest": "^27.4.1",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"git-commit-msg-linter": "^4.1.2",
"husky": "^7.0.4",
"jest": "^27.5.1",
"lint-staged": "^12.3.5",
"prettier": "^2.5.1",
"tsdx": "^0.14.1",
"typescript": "^4.6.2"
},
"dependencies": {
"uuid": "^8.3.2"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint "
],
"*.json": [
"prettier --write"
]
},
"pnpm": {
"neverBuiltDependencies": ["node-hid"]
},
"devDependencies": {
"@babel/cli": "^7.21.5",
"@babel/core": "^7.22.1",
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-typescript": "^7.16.7",
"@types/jest": "^27.4.1",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"git-commit-msg-linter": "^4.1.2",
"husky": "^7.0.4",
"jest": "^27.5.1",
"lint-staged": "^12.3.5",
"prettier": "^2.5.1",
"tsdx": "^0.14.1",
"typescript": "^4.6.2"
},
"dependencies": {
"uuid": "^8.3.2"
}
}
}
88 changes: 88 additions & 0 deletions packages/ur-registry-sui/__tests__/SuiSignHashRequest.test.ts

Large diffs are not rendered by default.

43 changes: 36 additions & 7 deletions packages/ur-registry-sui/__tests__/SuiSignature.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,15 @@ describe("sui-signature", () => {
"47e7b510784406dfa14d9fd13c3834128b49c56ddfc28edb02c5047219779adeed12017e2f9f116e83762e86f805c7311ea88fb403ff21900e069142b1fb310e",
"hex"
);
const requestId = Buffer.from(uuid.parse("9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"));
const requestId = Buffer.from(
uuid.parse("9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d")
);
const pubKey = Buffer.from(
"8e53e7b10656816de70824e3016fc1a277e77825e12825dc4f239f418ab2e04e",
"hex"
);

const suiSignature = new SuiSignature(
signature,
requestId,
pubKey
);
const suiSignature = new SuiSignature(signature, requestId, pubKey);

const cborHex = suiSignature.toCBOR().toString("hex");
const ur = suiSignature.toUREncoder(1000).nextPart();
Expand All @@ -29,9 +27,40 @@ describe("sui-signature", () => {
const suiSignatureDecoded = SuiSignature.fromCBOR(
Buffer.from(cborHex, "hex")
);
expect(uuid.stringify(suiSignatureDecoded.getRequestId())).toBe(uuid.stringify(requestId));
expect(uuid.stringify(suiSignatureDecoded.getRequestId())).toBe(
uuid.stringify(requestId)
);
expect(suiSignatureDecoded.getSignature().toString("hex")).toEqual(
"47e7b510784406dfa14d9fd13c3834128b49c56ddfc28edb02c5047219779adeed12017e2f9f116e83762e86f805c7311ea88fb403ff21900e069142b1fb310e"
);
});

it("should sign messageHash result is equal to the request", () => {
const signature = Buffer.from(
"2b2596eeb2bf21c0444ee4a85960447dbf5144eab6f855594fa836ed1e100bc6e32c04ad15e58d04ac669623f2125fe7154fe3caa7baaaf0243d64f88cd9e903",
"hex"
);
const requestId = Buffer.from("c267e8c4f363464e9f24c53aabf84fe0", "hex");
const requestIdHex = "c267e8c4f363464e9f24c53aabf84fe0";
const formattedUUID = [
requestIdHex.slice(0, 8),
requestIdHex.slice(8, 12),
requestIdHex.slice(12, 16),
requestIdHex.slice(16, 20),
requestIdHex.slice(20, 32),
].join("-");
const receiveRequestId = Buffer.from(uuid.parse(formattedUUID));
expect(requestId.toString("hex")).toBe(receiveRequestId.toString("hex"));
const pubKey = Buffer.from(
"edbe1b9b3b040ff88fbfa4ccda6f5f8d404ae7ffe35f9b220dec08679d5c336f",
"hex"
);

const suiSignature = new SuiSignature(signature, requestId, pubKey);

const ur = suiSignature.toUREncoder(1000).nextPart().toLocaleUpperCase();
expect(ur).toBe(
"UR:SUI-SIGNATURE/OTADTPDAGDSAIOVSSSWFIAFGGLNEDKSKFTPYYAGWVTAOHDFZDNDAMTWYPRRSCLRTFYGLVEPDHKHNFYKIRSGYFYWDRPYAGOHKGWPDENWECKBEBDSWVLDWAAPMBZVWLGAAPSIYMTCNWZBGHEVDBZGWVLSGOSRDPKWTDKFSIEYALKTAWLAXAXHDCXWERNCWNDFRAABSYAMYRSOXSFTNJLHELGFZGEVDZMVLHENDCPBTWPAYIONTHHEOJLDSLULEFW"
);
});
});
3 changes: 2 additions & 1 deletion packages/ur-registry-sui/src/RegistryType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import { RegistryType } from "@keystonehq/bc-ur-registry";

export const ExtendedRegistryTypes = {
SUI_SIGN_REQUEST: new RegistryType("sui-sign-request", 7101),
SUI_SIGNATURE: new RegistryType("sui-signature", 7102)
SUI_SIGNATURE: new RegistryType("sui-signature", 7102),
SUI_SIGN_HASH_REQUEST: new RegistryType("sui-sign-hash-request", 7103),
};
133 changes: 133 additions & 0 deletions packages/ur-registry-sui/src/SuiSignHashRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
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;

enum Keys {
requestId = 1,
messageHash,
derivationPaths,
addresses,
origin,
}

type signHashRequestProps = {
requestId?: Buffer;
messageHash: string;
derivationPaths: CryptoKeypath[];
addresses?: Buffer[];
origin?: string;
};

export class SuiSignHashRequest extends RegistryItem {
private requestId?: Buffer;
private messageHash: string;
private derivationPaths: CryptoKeypath[];
private addresses?: Buffer[];
private origin?: string;

getRegistryType = () => ExtendedRegistryTypes.SUI_SIGN_HASH_REQUEST;

constructor(args: signHashRequestProps) {
super();
this.requestId = args.requestId;
this.messageHash = args.messageHash;
this.derivationPaths = args.derivationPaths;
this.addresses = args.addresses;
this.origin = args.origin;
}

public getRequestId = () => this.requestId;
public getMessageHash = () => this.messageHash;
public getDerivationPaths = () =>
this.derivationPaths.map((key) => key.getPath());
public getAddresses = () => this.addresses;
public getOrigin = () => this.origin;

public toDataItem = () => {
const map: DataItemMap = {};
if (this.requestId) {
map[Keys.requestId] = new DataItem(
this.requestId,
RegistryTypes.UUID.getTag()
);
}
if (this.addresses) {
map[Keys.addresses] = this.addresses;
}
if (this.origin) {
map[Keys.origin] = this.origin;
}

map[Keys.messageHash] = this.messageHash;
map[Keys.derivationPaths] = this.derivationPaths.map((item) => {
const dataItem = item.toDataItem();
dataItem.setTag(item.getRegistryType().getTag());
return dataItem;
});
return new DataItem(map);
};

public static fromDataItem = (dataItem: DataItem) => {
const map = dataItem.getData();
const messageHash = map[Keys.messageHash];
const derivationPaths = map[Keys.derivationPaths].map((item: DataItem) =>
CryptoKeypath.fromDataItem(item)
);
const addresses = map[Keys.addresses] ? map[Keys.addresses] : undefined;
const requestId = map[Keys.requestId]
? map[Keys.requestId].getData()
: undefined;
const origin = map[Keys.origin] ? map[Keys.origin] : undefined;
return new SuiSignHashRequest({
requestId,
messageHash,
derivationPaths,
addresses,
origin,
});
};

public static fromCBOR = (_cborPayload: Buffer) => {
const dataItem = decodeToDataItem(_cborPayload);
return SuiSignHashRequest.fromDataItem(dataItem);
};

public static constructSuiRequest(
messageHash: string,
publicKeyHdPath: string[],
xfps: string[],
uuidString: string,
addresses?: Buffer[],
origin?: string
) {
const publicKeyHdPathObjects = publicKeyHdPath.map((path, index) => {
const paths = path.replace(/[m|M]\//, "").split("/");
const pathComponent = paths.map((path) => {
const index = parseInt(path.replace("'", ""));
let isHardened = false;
if (path.endsWith("'")) {
isHardened = true;
}
return new PathComponent({ index, hardened: isHardened });
});
return new CryptoKeypath(pathComponent, Buffer.from(xfps[index], "hex"));
});

return new SuiSignHashRequest({
requestId: Buffer.from(uuid.parse(uuidString)),
messageHash,
derivationPaths: publicKeyHdPathObjects,
addresses: addresses || undefined,
origin: origin || undefined,
});
}
}
10 changes: 3 additions & 7 deletions packages/ur-registry-sui/src/SuiSignature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
extend,
DataItem,
RegistryItem,
DataItemMap
DataItemMap,
} from "@keystonehq/bc-ur-registry";
import { ExtendedRegistryTypes } from "./RegistryType";

Expand All @@ -11,7 +11,7 @@ const { RegistryTypes, decodeToDataItem } = extend;
enum Keys {
requestId = 1,
signature,
publicKey
publicKey,
}

export class SuiSignature extends RegistryItem {
Expand All @@ -21,11 +21,7 @@ export class SuiSignature extends RegistryItem {

getRegistryType = () => ExtendedRegistryTypes.SUI_SIGNATURE;

constructor(
signature: Buffer,
requestId?: Buffer,
publicKey?: Buffer
) {
constructor(signature: Buffer, requestId?: Buffer, publicKey?: Buffer) {
super();
this.signature = signature;
this.requestId = requestId;
Expand Down
1 change: 1 addition & 0 deletions packages/ur-registry-sui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ patchTags(

export { SuiSignRequest } from "./SuiSignRequest";
export { SuiSignature } from "./SuiSignature";
export { SuiSignHashRequest } from "./SuiSignHashRequest";