Skip to content

Commit

Permalink
feat: add ur of BTC
Browse files Browse the repository at this point in the history
  • Loading branch information
renfengshi committed Mar 1, 2024
1 parent b42764d commit 3f2ed43
Show file tree
Hide file tree
Showing 11 changed files with 7,419 additions and 0 deletions.
15 changes: 15 additions & 0 deletions packages/ur-registry-btc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# BC-UR-Registry-BTC

This repository is the BTC extension of [bc-ur-registry](https://github.com/KeystoneHQ/ur-registry)

## Installing

To install, run:

```bash
yarn add @keystonehq/bc-ur-registry-btc
```

```bash
npm install --save @keystonehq/bc-ur-registry-btc
```
84 changes: 84 additions & 0 deletions packages/ur-registry-btc/__test__/BtcSignRequest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// @ts-nocheck

import { BtcSignRequest, CryptoKeypath, PathComponent, DataType } from "../src";
import * as uuid from "uuid";

describe("btc-sign-request", () => {
it("should generate btc-sign-request", () => {
const btcData = Buffer.from(
"48656c6c6f2063727970746f20776f726c6421",
"hex"
);

const signKeyPath = new CryptoKeypath(
[
new PathComponent({ index: 44, hardened: true }),
new PathComponent({ index: 0, hardened: true }),
new PathComponent({ index: 0, hardened: true }),
new PathComponent({ index: 0, hardened: false }),
new PathComponent({ index: 0, hardened: false }),
],
Buffer.from("78230804", "hex")
);

const btcRequestId = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d";
const idBuffer = uuid.parse(btcRequestId) as Uint8Array;

const btcSignRequest = new BtcSignRequest({
requestId: Buffer.from(idBuffer),
signData: btcData,
dataType: DataType.message,
derivationPaths: [signKeyPath],
addresses: ["1MjiyuupjWK5Ye13f8BAPvgby5WBuQXyJx"],
origin: "btc wallet",
});

const cborHex = btcSignRequest.toCBOR().toString("hex");
const ur = btcSignRequest.toUREncoder(1000).nextPart();

expect(ur).toBe(
"ur:btc-sign-request/oladtpdagdndcawmgtfrkigrpmndutdnbtkgfssbjnaogufdihjzjzjlcxiajpkkjojyjlcxktjljpjzieclaxadaalytaaddyoeadlecsdwykaeykaeykaewkaewkaocykscnayaaahlykscpehgtiminkkkpkpjoimhggrechkiheheoiyetfwfpgdkoioidkkechgfwkpgyhdkkgeksamimidjyiacxkthsjzjzihjypffpsbdy"
);
const btcSignRequestDecoded = BtcSignRequest.fromCBOR(
Buffer.from(cborHex, "hex")
);
expect(uuid.stringify(btcSignRequest.getRequestId())).toBe(btcRequestId);
expect(btcSignRequest.getOrigin()).toBe("btc wallet");
expect(btcSignRequestDecoded.getSignData().toString("hex")).toEqual(
"48656c6c6f2063727970746f20776f726c6421"
);
expect(btcSignRequestDecoded.getDerivationPaths()[0]).toEqual(
"44'/0'/0'/0/0"
);
expect(btcSignRequestDecoded.getDataype()).toBe(DataType.message);

expect(btcSignRequestDecoded.getAddress()[0]).toEqual(
"1MjiyuupjWK5Ye13f8BAPvgby5WBuQXyJx"
);
});

it("should construct an btcSignRequest object from string", () => {
const derivationHdPaths = ["m/44'/0'/0'/0/0"];
const xfps = ["78230804"];
const btcData = Buffer.from(
"48656c6c6f2063727970746f20776f726c6421",
"hex"
);
const requestID = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d";
const addresses = ["1MjiyuupjWK5Ye13f8BAPvgby5WBuQXyJx"];

const request = BtcSignRequest.constructBtcRequest(
requestID,
xfps,
btcData,
DataType.message,
derivationHdPaths,
addresses,
"btc wallet"
);
const ur = request.toUREncoder(1000).nextPart();
expect(ur).toBe(
"ur:btc-sign-request/oladtpdagdndcawmgtfrkigrpmndutdnbtkgfssbjnaogufdihjzjzjlcxiajpkkjojyjlcxktjljpjzieclaxadaalytaaddyoeadlecsdwykaeykaeykaewkaewkaocykscnayaaahlykscpehgtiminkkkpkpjoimhggrechkiheheoiyetfwfpgdkoioidkkechgfwkpgyhdkkgeksamimidjyiacxkthsjzjzihjypffpsbdy"
);
});
});
38 changes: 38 additions & 0 deletions packages/ur-registry-btc/__test__/BtcSignature.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// @ts-nocheck

import { BtcSignature } from "../src";
import * as uuid from "uuid";

describe("btc-signature", () => {
it("should generate btc-signature", () => {
const signature = Buffer.from(
"47e7b510784406dfa14d9fd13c3834128b49c56ddfc28edb02c5047219779adeed12017e2f9f116e83762e86f805c7311ea88fb403ff21900e069142b1fb310e",
"hex"
);
const requestId = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d";
const idBuffer = uuid.parse(requestId) as Uint8Array;
const pubKey = Buffer.from(
"8e53e7b10656816de70824e3016fc1a277e77825e12825dc4f239f418ab2e04e",
"hex"
);

const btcSignature = new BtcSignature(
signature,
Buffer.from(idBuffer),
pubKey
);

const cborHex = btcSignature.toCBOR().toString("hex");
const ur = btcSignature.toUREncoder(1000).nextPart();
expect(ur).toBe(
"ur:btc-signature/otadtpdagdndcawmgtfrkigrpmndutdnbtkgfssbjnaohdfzflvdrebeksfyamuroygtnettfneteebglugaskjnursamnuyaoskaajpcfktnyuewebgadkbdlnebyjtlskodmlnyaahstehckpdmyqzaxzmclmhbaammefwpazoehbaaxhdcxmnguvdpaamhflyjnvdaydkvladjlseoektvdksdavydedauogwcnnefpleprvtgloynbcfut"
);
const btcSignatureDecoded = BtcSignature.fromCBOR(
Buffer.from(cborHex, "hex")
);
expect(uuid.stringify(btcSignatureDecoded.getRequestId())).toBe(requestId);
expect(btcSignatureDecoded.getSignature().toString("hex")).toEqual(
"47e7b510784406dfa14d9fd13c3834128b49c56ddfc28edb02c5047219779adeed12017e2f9f116e83762e86f805c7311ea88fb403ff21900e069142b1fb310e"
);
});
});
6 changes: 6 additions & 0 deletions packages/ur-registry-btc/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
presets: [
["@babel/preset-env", { targets: { node: "current" } }],
"@babel/preset-typescript",
],
};
37 changes: 37 additions & 0 deletions packages/ur-registry-btc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@keystonehq/bc-ur-registry-btc",
"version": "0.1.0",
"description": "bc-ur-registry extension for BTC",
"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": "Renfeng Shi<[email protected]>",
"license": "ISC",
"dependencies": {
"@keystonehq/bc-ur-registry": "^0.5.4",
"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": ""
}
155 changes: 155 additions & 0 deletions packages/ur-registry-btc/src/BtcSignRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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 DataType {
message = 1,
}

enum Keys {
requestId = 1,
signData,
dataType,
derivationPaths,
addresses,
origin,
}

type signRequestProps = {
requestId: Buffer;
signData: Buffer;
dataType: DataType;
derivationPaths: CryptoKeypath[];
addresses?: string[];
origin?: string;
};

export class BtcSignRequest extends RegistryItem {
private requestId: Buffer;
private signData: Buffer;
private dataType: DataType;
private derivationPaths: CryptoKeypath[];
private addresses?: string[];
private origin?: string;

getRegistryType = () => ExtendedRegistryTypes.BTC_SIGN_REQUEST;

constructor(args: signRequestProps) {
super();
this.requestId = args.requestId;
this.signData = args.signData;
this.dataType = args.dataType;
this.derivationPaths = args.derivationPaths;
this.addresses = args.addresses;
this.origin = args.origin;
}

public getRequestId = () => this.requestId;
public getSignData = () => this.signData;
public getDataype = () => this.dataType;
public getDerivationPaths = () =>
this.derivationPaths.map((key) => key.getPath());
public getAddress = () => this.addresses;
public getOrigin = () => this.origin;

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

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

if (this.addresses) {
map[Keys.addresses] = this.addresses;
}

if (this.origin) {
map[Keys.origin] = this.origin;
}

return new DataItem(map);
};

public static fromDataItem = (dataItem: DataItem) => {
const map = dataItem.getData();

const requestId = map[Keys.requestId]
? map[Keys.requestId].getData()
: undefined;

const signData = map[Keys.signData];

const dataType = map[Keys.dataType];

const derivationPaths = map[Keys.derivationPaths].map((item: DataItem) =>
CryptoKeypath.fromDataItem(item)
);

const addresses = map[Keys.addresses] ? map[Keys.addresses] : undefined;

const origin = map[Keys.origin] ? map[Keys.origin] : undefined;

return new BtcSignRequest({
requestId,
signData,
dataType,
derivationPaths,
addresses,
origin,
});
};

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

public static constructBtcRequest(
uuidString: string,
xfps: string[],
signData: Buffer,
dataType: DataType,
derivationHDPaths: string[],
addresses?: string[],
origin?: string
) {
const derivationHdPathObjects = derivationHDPaths.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 BtcSignRequest({
requestId: Buffer.from(uuid.parse(uuidString) as Uint8Array),
signData,
dataType: dataType || DataType.message,
derivationPaths: derivationHdPathObjects,
addresses: addresses || undefined,
origin: origin || undefined,
});
}
}
57 changes: 57 additions & 0 deletions packages/ur-registry-btc/src/BtcSignature.ts
Original file line number Diff line number Diff line change
@@ -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,
publicKey,
}

export class BtcSignature extends RegistryItem {
private requestId: Buffer;
private signature: Buffer;
private publicKey: Buffer;

getRegistryType = () => ExtendedRegistryTypes.BTC_SIGNATURE;

constructor(signature: Buffer, requestId: Buffer, publicKey: Buffer) {
super();
this.signature = signature;
this.requestId = requestId;
this.publicKey = publicKey;
}

public getRequestId = () => this.requestId;
public getSignature = () => this.signature;
public getPublicKey = () => this.publicKey;
public toDataItem = () => {
const map: DataItemMap = {};
map[Keys.requestId] = new DataItem(
this.requestId,
RegistryTypes.UUID.getTag()
);
map[Keys.signature] = this.signature;
map[Keys.publicKey] = this.publicKey;
return new DataItem(map);
};

public static fromDataItem = (dataItem: DataItem) => {
const map = dataItem.getData();
const signature = map[Keys.signature];
const requestId = map[Keys.requestId].getData();
const authenticationPublicKey = map[Keys.publicKey];
return new BtcSignature(signature, requestId, authenticationPublicKey);
};

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

0 comments on commit 3f2ed43

Please sign in to comment.