diff options
author | polwex <polwex@sortug.com> | 2025-07-16 11:59:08 +0700 |
---|---|---|
committer | polwex <polwex@sortug.com> | 2025-07-16 11:59:08 +0700 |
commit | 9c2fba56e0f68f976c1abe486f9fd3c6e93b437e (patch) | |
tree | 22470efb92a9f1b673136d7c0bdadc06abb3ba35 | |
parent | 8ae0d2779c26f74e64a1db2b028bd2ac2f599cb4 (diff) |
'm'
-rw-r--r-- | components/login/ShipCredsForm.tsx | 39 | ||||
-rw-r--r-- | lib/urbit/constants.ts | 288 | ||||
-rw-r--r-- | lib/urbit/wallet.ts | 79 | ||||
-rw-r--r-- | lib/utils/bit.ts | 35 |
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"); +} |