diff --git a/README.md b/README.md index 1481a772..6ffa3b1d 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Snort supports the following NIP's: - [x] NIP-50: Search - [x] NIP-51: Lists - [x] NIP-53: Live Events +- [x] NIP-55: Android signer application - [x] NIP-57: Zaps - [x] NIP-58: Badges - [x] NIP-59: Gift Wrap diff --git a/packages/app/package.json b/packages/app/package.json index fe51c1cc..308d37ff 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -105,6 +105,7 @@ "@types/webtorrent": "^0.109.3", "@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/parser": "^6.1.0", + "@vitejs/plugin-basic-ssl": "^1.2.0", "@vitejs/plugin-react": "^4.2.0", "@webbtc/webln-types": "^3.0.0", "@webscopeio/react-textarea-autocomplete": "^4.9.2", diff --git a/packages/app/src/Pages/onboarding/start.tsx b/packages/app/src/Pages/onboarding/start.tsx index d3b5f586..daa3a14a 100644 --- a/packages/app/src/Pages/onboarding/start.tsx +++ b/packages/app/src/Pages/onboarding/start.tsx @@ -1,5 +1,4 @@ -import { unwrap } from "@snort/shared"; -import { NotEncrypted } from "@snort/system"; +import { Nip7Signer, Nip55Signer, NotEncrypted } from "@snort/system"; import { SnortContext } from "@snort/system-react"; import classNames from "classnames"; import { FormEvent, useContext, useState } from "react"; @@ -16,6 +15,8 @@ import { NewUserState } from "."; const NSEC_NPUB_REGEX = /(nsec1|npub1)[a-zA-Z0-9]{20,65}/gi; +const signer = new Nip55Signer(); + export function SignIn() { const navigate = useNavigate(); const { formatMessage } = useIntl(); @@ -25,15 +26,23 @@ export function SignIn() { const loginHandler = useLoginHandler(); const hasNip7 = "nostr" in window; + const hasNip55 = true; + async function doNip07Login() { - /*const relays = - "getRelays" in unwrap(window.nostr) ? await unwrap(window.nostr?.getRelays).call(window.nostr) : undefined;*/ - const pubKey = await unwrap(window.nostr).getPublicKey(); + const signer = new Nip7Signer(); + const pubKey = await signer.getPubKey(); LoginStore.loginWithPubkey(pubKey, LoginSessionType.Nip7); trackEvent("Login", { type: "NIP7" }); navigate("/"); } + async function doNip55Login() { + const pubKey = await signer.getPubKey(); + LoginStore.loginWithPubkey(pubKey, LoginSessionType.Nip55); + trackEvent("Login", { type: "NIP55" }); + navigate("/"); + } + async function onSubmit(e) { e.preventDefault(); doLogin(key); @@ -69,7 +78,7 @@ export function SignIn() { } }; - const nip7Login = hasNip7 && !useKey; + const signerExtLogin = (hasNip7 || hasNip55) && !useKey; return (
@@ -77,10 +86,10 @@ export function SignIn() {

- {nip7Login && } + {signerExtLogin && }
-
- {hasNip7 && !useKey && ( +
+ {signerExtLogin && ( <>
@@ -88,6 +97,12 @@ export function SignIn() {
+ +
+ +
+ +
@@ -96,13 +111,12 @@ export function SignIn() { )} - {(!hasNip7 || useKey) && ( + {(!signerExtLogin || useKey) && (
void; reject: () => void }> = []; + + init(): Promise { + // nothing here + return Promise.resolve(); + } + + async getPubKey() { + let pk = await this.#call("get_public_key", "signature"); + if (pk.startsWith("npub")) { + pk = bech32ToHex(pk); + } + return pk; + } + + async nip4Encrypt(content: string, key: string) { + return await this.#call("nip04_encrypt", "signature", content, new Map([["pubkey", key]])); + } + + async nip4Decrypt(content: string, otherKey: string) { + return await this.#call("nip04_decrypt", "signature", content, new Map([["pubkey", otherKey]])); + } + + async nip44Encrypt(content: string, key: string) { + return await this.#call("nip44_encrypt", "signature", content, new Map([["pubkey", key]])); + } + + async nip44Decrypt(content: string, otherKey: string) { + return await this.#call("nip44_decrypt", "signature", content, new Map([["pubkey", otherKey]])); + } + + async sign(ev: NostrEvent | NotSignedNostrEvent) { + const evRet = await this.#call("sign_event", "event", ev); + return JSON.parse(evRet); + } + + get supports(): string[] { + return ["nip04", "nip44"]; + } + + #call( + method: string, + returnType: string, + obj?: NostrEvent | NotSignedNostrEvent | string, + otherParams?: Map, + ) { + const id = uuid(); + const objString = typeof obj === "string" ? obj : obj != undefined ? JSON.stringify(obj) : undefined; + + const params = new URLSearchParams(); + params.append("compressionType", "none"); + params.append("returnType", returnType); + params.append("type", method); + if (otherParams) { + for (const [k, v] of otherParams) { + params.append(k, v); + } + } + + return new Promise((resolve, reject) => { + const t = setInterval(async () => { + if (document.hasFocus()) { + const text = await navigator.clipboard.readText(); + if (text) { + this.#log("Response: %s", text); + await navigator.clipboard.writeText(""); + resolve(text); + clearInterval(t); + } + } + }, 500); + + const dst = `nostrsigner:${objString ?? ""}?${params.toString()}`; + this.#log("Sending command %s, %s", id, dst); + globalThis.location.href = dst; + }); + } +} diff --git a/packages/system/src/index.ts b/packages/system/src/index.ts index fad89b48..6e197793 100644 --- a/packages/system/src/index.ts +++ b/packages/system/src/index.ts @@ -33,6 +33,7 @@ export * from "./impl/nip10"; export * from "./impl/nip44"; export * from "./impl/nip46"; export * from "./impl/nip57"; +export * from "./impl/nip55"; export * from "./cache/index"; export * from "./cache/user-relays"; diff --git a/yarn.lock b/yarn.lock index 4fe39d8a..cb18f098 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4724,6 +4724,7 @@ __metadata: "@typescript-eslint/eslint-plugin": "npm:^6.1.0" "@typescript-eslint/parser": "npm:^6.1.0" "@uidotdev/usehooks": "npm:^2.4.1" + "@vitejs/plugin-basic-ssl": "npm:^1.2.0" "@vitejs/plugin-react": "npm:^4.2.0" "@void-cat/api": "npm:^1.0.12" "@webbtc/webln-types": "npm:^3.0.0" @@ -4823,7 +4824,7 @@ __metadata: resolution: "@snort/system-react@workspace:packages/system-react" dependencies: "@snort/shared": "npm:^1.0.17" - "@snort/system": "npm:^1.5.6" + "@snort/system": "npm:^1.5.7" "@types/react": "npm:^18.2.14" react: "npm:^18.2.0" typescript: "npm:^5.2.2" @@ -4858,7 +4859,7 @@ __metadata: languageName: unknown linkType: soft -"@snort/system@npm:^1.0.21, @snort/system@npm:^1.2.11, @snort/system@npm:^1.5.2, @snort/system@npm:^1.5.6, @snort/system@workspace:*, @snort/system@workspace:packages/system": +"@snort/system@npm:^1.0.21, @snort/system@npm:^1.2.11, @snort/system@npm:^1.5.2, @snort/system@npm:^1.5.7, @snort/system@workspace:*, @snort/system@workspace:packages/system": version: 0.0.0-use.local resolution: "@snort/system@workspace:packages/system" dependencies: @@ -4898,7 +4899,7 @@ __metadata: "@lightninglabs/lnc-web": "npm:^0.3.1-alpha" "@scure/base": "npm:^1.1.6" "@snort/shared": "npm:^1.0.17" - "@snort/system": "npm:^1.5.6" + "@snort/system": "npm:^1.5.7" "@types/debug": "npm:^4.1.12" "@webbtc/webln-types": "npm:^3.0.0" debug: "npm:^4.3.4" @@ -6011,6 +6012,15 @@ __metadata: languageName: node linkType: hard +"@vitejs/plugin-basic-ssl@npm:^1.2.0": + version: 1.2.0 + resolution: "@vitejs/plugin-basic-ssl@npm:1.2.0" + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + checksum: 10/34def4ab7838901f1f51bbff21fefac5f56346331f4ff1a8a3059d68e4cd43b62e1581a5ad39971d5d908343ee3217e782a0b506d97e0653c3373e27757ecedf + languageName: node + linkType: hard + "@vitejs/plugin-react@npm:^4.2.0": version: 4.2.0 resolution: "@vitejs/plugin-react@npm:4.2.0"