summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpolwex <polwex@sortug.com>2025-07-16 11:59:08 +0700
committerpolwex <polwex@sortug.com>2025-07-16 11:59:08 +0700
commit9c2fba56e0f68f976c1abe486f9fd3c6e93b437e (patch)
tree22470efb92a9f1b673136d7c0bdadc06abb3ba35
parent8ae0d2779c26f74e64a1db2b028bd2ac2f599cb4 (diff)
'm'
-rw-r--r--components/login/ShipCredsForm.tsx39
-rw-r--r--lib/urbit/constants.ts288
-rw-r--r--lib/urbit/wallet.ts79
-rw-r--r--lib/utils/bit.ts35
4 files changed, 440 insertions, 1 deletions
diff --git a/components/login/ShipCredsForm.tsx b/components/login/ShipCredsForm.tsx
index ac58e86..d84a474 100644
--- a/components/login/ShipCredsForm.tsx
+++ b/components/login/ShipCredsForm.tsx
@@ -9,8 +9,15 @@ import {
} from "react-native";
import PrimaryButton from "../PrimaryButton";
import { useState } from "react";
-import { createFancyPasskey, pkDecrypt, pkEncrypt } from "@/lib/passkey";
+import {
+ createFancyPasskey,
+ createPasskey,
+ pkDecrypt,
+ pkEncrypt,
+} from "@/lib/passkey";
import toast, { Toaster } from "react-hot-toast";
+import { makeWalletFromP } from "@/lib/urbit/wallet";
+import { hex2buf } from "@/lib/utils/bit";
export function ShipForm() {
const colors = lightColors;
@@ -20,11 +27,35 @@ export function ShipForm() {
const [encryptedTicket, setenc] = useState("");
const [isLoading, setIsLoading] = useState(false);
async function onSubmit() {
+ const wallet = await makeWalletFromP(patp, ticket);
+ console.log("seed", wallet.ownership.seed);
+ const privkey = wallet.ownership.keys.private;
+ const clean = privkey.replace(/^0x/i, "");
+ const byteLength = clean.length / 2;
+ toast(`bytes: ${byteLength}`);
+ const bufer = hex2buf(privkey);
+ toast(`buffer size: ${bufer.byteLength}`);
+ const ba = Uint8Array.fromHex(clean);
+ toast(`uintarray byte length: ${ba.byteLength}`);
+ console.log({
+ bufl: bufer.length,
+ bufbl: bufer.byteLength,
+ ul: ba.length,
+ ubl: ba.byteLength,
+ });
+ }
+ async function makeFPk() {
console.log("running", { patp, ticket });
const passkey = await createFancyPasskey();
console.log({ passkey });
toast(passkey);
}
+ async function makePk() {
+ console.log("running", { patp, ticket });
+ const passkey = await createPasskey();
+ if ("error" in passkey) return toast.error(passkey.error);
+ console.log({ passkey });
+ }
async function enc() {
const done = await pkEncrypt(ticket);
console.log({ encryptedTicket });
@@ -65,6 +96,12 @@ export function ShipForm() {
isLoading={isLoading}
/>
<PrimaryButton
+ label="Make Passkey"
+ onPress={makePk}
+ style={{ marginTop: 0 }}
+ isLoading={isLoading}
+ />
+ <PrimaryButton
label="Encrypt"
onPress={enc}
style={{ marginTop: 0 }}
diff --git a/lib/urbit/constants.ts b/lib/urbit/constants.ts
new file mode 100644
index 0000000..38ece6c
--- /dev/null
+++ b/lib/urbit/constants.ts
@@ -0,0 +1,288 @@
+// const isDevelopment = import.meta.env.DEV === true;
+const isDevelopment = true;
+export const CRYPTO_SUITE_VERSION = 1;
+const CHECK_BLOCK_EVERY_MS = isDevelopment ? 1000 : 10000;
+const DEFAULT_GAS_PRICE_GWEI = 40;
+const MAX_GAS_PRICE_GWEI = 400;
+
+const MIN_GALAXY = 0;
+const MAX_GALAXY = 255;
+const MIN_STAR = 256;
+const MAX_STAR = 65535;
+const MIN_PLANET = 65536;
+const MAX_PLANET = 4294967297;
+
+const ZOD = MIN_GALAXY;
+
+const PLANET_ENTROPY_BITS = 64;
+const STAR_ENTROPY_BITS = 128;
+const GALAXY_ENTROPY_BITS = 384;
+
+const SEED_ENTROPY_BITS = 128;
+
+//TODO move into azimuth-js
+const GAS_LIMITS = {
+ SPAWN: 250000,
+ TRANSFER: 560000, //NOTE biggest, also update gas tank's max
+ CONFIGURE_KEYS: 100000,
+ SET_PROXY: 150000,
+ //
+ ESCAPE: 115000, //NOTE low sample size
+ ADOPT: 100000, //NOTE low sample size
+ CANCEL_ESCAPE: 200000, //NOTE no samples
+ REJECT: 200000, //NOTE no samples
+ DETACH: 200000, //NOTE no samples
+ //
+ GIFT_PLANET: 450000, //NOTE low sample size, //NOTE also update in gas tank
+ //
+ TRANSFER_LOCKUP: 700000, //NOTE low sample size
+ //
+ SEND_ETH: 21000,
+ //
+ DEFAULT: 550000,
+};
+
+// TODO: this is walletgen-ui specific, move into a wallet router later
+const GEN_STATES = {
+ ENY_NOSTART: Symbol("ENY_NOSTART"),
+ ENY_PENDING: Symbol("ENY_PENDING"),
+ ENY_SUCCESS: Symbol("ENY_SUCCESS"),
+ ENY_FAILURE: Symbol("ENY_FAILURE"),
+ DERIVATION_NOSTART: Symbol("DERIVATION_NOSTART"),
+ DERIVATION_PENDING: Symbol("DERIVATION_PENDING"),
+ DERIVATION_SUCCESS: Symbol("DERIVATION_SUCCESS"),
+ DERIVATION_FAILURE: Symbol("DERIVATION_FAILURE"),
+ PAPER_NOSTART: Symbol("PAPER_NOSTART"),
+ PAPER_PENDING: Symbol("PAPER_PENDING"),
+ PAPER_SUCCESS: Symbol("PAPER_SUCCESS"),
+ PAPER_FAILURE: Symbol("PAPER_FAILURE"),
+};
+
+const BUTTON_STATES = {
+ NOSTART: Symbol("NOSTART"),
+ SUCCESS: Symbol("SUCCESS"),
+ ERROR: Symbol("ERROR"),
+ PENDING: Symbol("PENDING"),
+};
+
+const PROFILE_STATES = {
+ NOSTART: Symbol("NOSTART"),
+ UPLOAD_SUCCESS: Symbol("UPLOAD_SUCCESS"),
+ UPLOAD_ERROR: Symbol("UPLOAD_ERROR"),
+ INPUT_SUCCESS: Symbol("INPUT_SUCCESS"),
+ INPUT_ERROR: Symbol("INPUT_ERROR"),
+};
+
+const PROGRESS_ANIMATION_DELAY_MS = 500; // .animated-width
+
+const DEFAULT_HD_PATH = "m/44'/60'/0'/0/0";
+const ETH_ZERO_ADDR = "0x0000000000000000000000000000000000000000";
+const ETH_ZERO_ADDR_SHORT = "0x0";
+
+const LINEAR_STAR_RELEASE = "0x86cd9cd0992f04231751e3761de45cecea5d1801";
+const CONDITIONAL_STAR_RELEASE = "0x8c241098c3d3498fe1261421633fd57986d74aea";
+
+const WALLET_TYPES = {
+ MNEMONIC: Symbol("MNEMONIC"),
+ TICKET: Symbol("TICKET"),
+ SHARDS: Symbol("SHARDS"),
+ LEDGER: Symbol("LEDGER"),
+ TREZOR: Symbol("TREZOR"),
+ PRIVATE_KEY: Symbol("PRIVATE_KEY"),
+ KEYSTORE: Symbol("KEYSTORE"),
+ METAMASK: Symbol("METAMASK"),
+ WALLET_CONNECT: Symbol("WALLET_CONNECT"),
+};
+
+const NONCUSTODIAL_WALLETS = new Set([
+ WALLET_TYPES.METAMASK,
+ WALLET_TYPES.TREZOR,
+ WALLET_TYPES.LEDGER,
+ WALLET_TYPES.WALLET_CONNECT,
+]);
+
+const ROLLER_HOSTS = {
+ LOCAL: "localhost",
+ GOERLI: "roller-tmp.urbit.org",
+ MAINNET: "roller.urbit.org",
+};
+
+const ROLLER_PATH = "/v1/roller";
+
+const POINT_DOMINIONS = {
+ L1: "l1",
+ L2: "l2",
+ SPAWN: "spawn",
+};
+
+const POINT_PROXIES = {
+ OWN: "own",
+ MANAGE: "manage",
+ TRANSFER: "transfer",
+ VOTE: "vote",
+ SPAWN: "spawn",
+};
+
+// In ms
+const DEFAULT_FADE_TIMEOUT = 300;
+const MASTER_KEY_DURATION = 850;
+
+const INVITES_PER_PAGE = 7;
+const DEFAULT_NUM_INVITES = 5;
+const DEFAULT_CSV_NAME = "urbit_invites.csv";
+
+const TRANSACTION_STATUS_ICONS: {
+ [key: string]:
+ | "Checkmark"
+ | "ArrowNorth"
+ | "Clock"
+ | "ExclaimationMark"
+ | "NullIcon";
+} = {
+ confirmed: "Checkmark",
+ pending: "Clock",
+ sending: "ArrowNorth",
+ failed: "ExclaimationMark",
+ unknown: "NullIcon",
+};
+
+const TRANSACTION_TYPE_ICONS: {
+ [key: string]:
+ | "ArrowRefresh"
+ | "BootNode"
+ | "Delete"
+ | "EjectedSponsor"
+ | "EscapeApproved"
+ | "EscapeRejected"
+ | "EscapeRequested"
+ | "ShipSpawned"
+ | "Swap";
+} = {
+ adopt: "EscapeApproved",
+ "cancel-escape": "EscapeRejected",
+ "cancel-transfer": "Delete",
+ "configure-keys": "BootNode",
+ detach: "EjectedSponsor",
+ escape: "EscapeRequested",
+ migrate: "Swap",
+ reject: "EscapeRejected",
+ "set-management-proxy": "ArrowRefresh",
+ "set-spawn-proxy": "ArrowRefresh",
+ "set-transfer-proxy": "ArrowRefresh",
+ "set-voting-proxy": "ArrowRefresh",
+ spawn: "ShipSpawned",
+ "transfer-point": "Swap",
+};
+
+const TRANSACTION_TYPE_TITLES: {
+ [key: string]:
+ | "Ship Sponsored"
+ | "Escape Canceled"
+ | "Transfer Canceled"
+ | "Network Keys Configured"
+ | "Sponsee Detached"
+ | "Escaped Sponsor"
+ | "Migrating to Layer 2"
+ | "Sponsorship Rejected"
+ | "Management Proxy Changed"
+ | "Spawn Proxy Changed"
+ | "Transfer Proxy Changed"
+ | "Voting Proxy Changed"
+ | "Ship Spawned"
+ | "Ship Transferred";
+} = {
+ adopt: "Ship Sponsored",
+ "cancel-escape": "Escape Canceled",
+ "cancel-transfer": "Transfer Canceled",
+ "configure-keys": "Network Keys Configured",
+ detach: "Sponsee Detached",
+ escape: "Escaped Sponsor",
+ migrate: "Migrating to Layer 2",
+ reject: "Sponsorship Rejected",
+ "set-management-proxy": "Management Proxy Changed",
+ "set-spawn-proxy": "Spawn Proxy Changed",
+ "set-transfer-proxy": "Transfer Proxy Changed",
+ "set-voting-proxy": "Voting Proxy Changed",
+ spawn: "Ship Spawned",
+ "transfer-point": "Ship Transferred",
+};
+const DUMMY_L2_ADDRESS = "0x1111111111111111111111111111111111111111";
+
+const ONE_SECOND = 1000;
+const TWO_SECONDS = 2 * ONE_SECOND;
+const TEN_SECONDS = 10 * ONE_SECOND;
+const ONE_MINUTE = 60 * ONE_SECOND;
+
+const MASTER_TICKET_TOOLTIP =
+ "Your Master Ticket is your 4-word password for your Urbit";
+
+const PASSPORT_TOOLTIP =
+ "Your Passport contains all of the address information for your wallet";
+
+const LOCKUP_TOOLTIP =
+ "Stars in a lockup schedule can be withdrawn after a set time duration. The duration is predetermined by the issuer";
+
+// the initial network key revision is always 1
+const INITIAL_NETWORK_KEY_REVISION = 1;
+
+const EIP1559_TRANSACTION_TYPE = 2;
+
+// Chain IDs
+// https://chainlist.org/?testnets=true
+const ETHEREUM_MAINNET_CHAIN_ID = "0x1";
+const ETHEREUM_GOERLI_CHAIN_ID = "0x5";
+const ETHEREUM_LOCAL_CHAIN_ID = "0x539";
+
+export {
+ CHECK_BLOCK_EVERY_MS,
+ DEFAULT_GAS_PRICE_GWEI,
+ DEFAULT_HD_PATH,
+ EIP1559_TRANSACTION_TYPE,
+ ETH_ZERO_ADDR,
+ ETH_ZERO_ADDR_SHORT,
+ LINEAR_STAR_RELEASE,
+ CONDITIONAL_STAR_RELEASE,
+ MAX_GAS_PRICE_GWEI,
+ GAS_LIMITS,
+ GEN_STATES,
+ BUTTON_STATES,
+ PROFILE_STATES,
+ MIN_GALAXY,
+ MAX_GALAXY,
+ MIN_STAR,
+ MAX_STAR,
+ MIN_PLANET,
+ MAX_PLANET,
+ NONCUSTODIAL_WALLETS,
+ PLANET_ENTROPY_BITS,
+ STAR_ENTROPY_BITS,
+ GALAXY_ENTROPY_BITS,
+ SEED_ENTROPY_BITS,
+ WALLET_TYPES,
+ ZOD,
+ PROGRESS_ANIMATION_DELAY_MS,
+ ROLLER_HOSTS,
+ ROLLER_PATH,
+ POINT_DOMINIONS,
+ POINT_PROXIES,
+ DEFAULT_FADE_TIMEOUT,
+ MASTER_KEY_DURATION,
+ INVITES_PER_PAGE,
+ DEFAULT_NUM_INVITES,
+ DEFAULT_CSV_NAME,
+ TRANSACTION_STATUS_ICONS,
+ TRANSACTION_TYPE_ICONS,
+ TRANSACTION_TYPE_TITLES,
+ DUMMY_L2_ADDRESS,
+ ONE_SECOND,
+ TWO_SECONDS,
+ TEN_SECONDS,
+ ONE_MINUTE,
+ MASTER_TICKET_TOOLTIP,
+ PASSPORT_TOOLTIP,
+ LOCKUP_TOOLTIP,
+ INITIAL_NETWORK_KEY_REVISION,
+ ETHEREUM_MAINNET_CHAIN_ID,
+ ETHEREUM_GOERLI_CHAIN_ID,
+ ETHEREUM_LOCAL_CHAIN_ID,
+};
diff --git a/lib/urbit/wallet.ts b/lib/urbit/wallet.ts
new file mode 100644
index 0000000..c29da48
--- /dev/null
+++ b/lib/urbit/wallet.ts
@@ -0,0 +1,79 @@
+import { generateWallet } from "urbit-key-generation";
+import * as ob from "urbit-ob";
+
+import {
+ GALAXY_ENTROPY_BITS,
+ MIN_PLANET,
+ MIN_STAR,
+ PLANET_ENTROPY_BITS,
+ STAR_ENTROPY_BITS,
+ SEED_ENTROPY_BITS,
+} from "./constants";
+import { shas } from "@/lib/utils/bit";
+
+const SEED_LENGTH_BYTES = SEED_ENTROPY_BITS / 8;
+const getTicketBitSize = (point: number) =>
+ point < MIN_STAR
+ ? GALAXY_ENTROPY_BITS
+ : point < MIN_PLANET
+ ? STAR_ENTROPY_BITS
+ : PLANET_ENTROPY_BITS;
+
+export const stripHexPrefix = (hex: string) => {
+ return hex.startsWith("0x") ? hex.slice(2) : hex;
+};
+export const makeDeterministicTicket = (point: number, seed: string) => {
+ const bits = getTicketBitSize(point);
+
+ const bytes = bits / 8;
+
+ const pointSalt = Buffer.concat([
+ Buffer.from(point.toString()),
+ Buffer.from("invites"),
+ ]);
+ const normalizedSeed = stripHexPrefix(seed);
+ const entropy = shas(Buffer.from(normalizedSeed, "hex"), pointSalt);
+
+ const buf = entropy.slice(0, bytes);
+ const patq = ob.hex2patq(buf.toString("hex"));
+ return patq;
+};
+
+// return a wallet object
+export const makeWalletFromP = async (patp: string, ticket: string) => {
+ const point = Number(ob.patp2dec(patp));
+ return await makeWallet({ point, ticket });
+};
+export const makeWallet = async (data: {
+ point: number;
+ ticket: string;
+ boot?: boolean;
+ revision?: number;
+}) => {
+ const config = {
+ ticket: data.ticket,
+ seedSize: SEED_LENGTH_BYTES,
+ point: data.point,
+ password: "",
+ revision: data.revision || 1,
+ boot: data.boot || false,
+ };
+
+ // This is here to notify anyone who opens console because the thread
+ // hangs, blocking UI updates so this cannot be done in the UI
+ console.log("Generating Wallet for point address: ", data.point);
+
+ const wallet = await generateWallet(config);
+ console.log({ wallet });
+
+ return wallet;
+ // return new Promise(async (resolve, reject) => {
+ // // Use a web worker to process the data
+ // try {
+ // const processed = await walletgenWorker.generate(JSON.stringify(config));
+ // resolve(processed);
+ // } catch (error) {
+ // reject(error);
+ // }
+ // });
+};
diff --git a/lib/utils/bit.ts b/lib/utils/bit.ts
new file mode 100644
index 0000000..bc1ed1a
--- /dev/null
+++ b/lib/utils/bit.ts
@@ -0,0 +1,35 @@
+import { sha256 } from "urbit-key-generation/src/utils";
+
+export function shas(buf: Buffer, salt: Buffer) {
+ return sha256(xor(salt, sha256(buf)));
+}
+
+export function xor(a: Buffer, b: Buffer) {
+ if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
+ console.log("a", a);
+ console.log("b", b);
+ throw new Error("only xor buffers!");
+ }
+ const length = Math.max(a.byteLength, b.byteLength);
+ const result = new Uint8Array(length);
+ for (let i = 0; i < length; i++) {
+ result[i] = a[i] ^ b[i];
+ }
+ return result;
+}
+
+export function shaf(buf: Buffer, salt: Buffer) {
+ const result = shas(buf, salt);
+ const halfway = result.length / 2;
+ const front = result.slice(0, halfway);
+ const back = result.slice(halfway, result.length);
+ return xor(front, back);
+}
+
+export function hex2buf(hex: string) {
+ return Buffer.from(hex, "hex").reverse();
+}
+
+export function buf2hex(buf: Uint8Array) {
+ return Buffer.from(buf).reverse().toString("hex");
+}