From 6cec2f998ef993688ad21502eb70ba54df74496b Mon Sep 17 00:00:00 2001 From: 0xzoz Date: Tue, 10 Jan 2023 13:44:24 -0800 Subject: [PATCH 1/7] feat: add sui signing --- packages/cacao/src/cacao.ts | 48 ++++++++++++++++++++++++++++++- packages/cacao/src/index.ts | 1 + packages/cacao/src/siwx/siwSui.ts | 25 ++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 packages/cacao/src/siwx/siwSui.ts diff --git a/packages/cacao/src/cacao.ts b/packages/cacao/src/cacao.ts index bcf3bb6c..051da5ff 100644 --- a/packages/cacao/src/cacao.ts +++ b/packages/cacao/src/cacao.ts @@ -5,6 +5,7 @@ import * as Block from 'multiformats/block' import { sha256 as hasher } from 'multiformats/hashes/sha2' import { SiweMessage } from './siwx/siwe.js' import { SiwsMessage } from './siwx/siws.js' +import { SiwSuiMessage } from './siwx/siwSui.js' import { SiwTezosMessage } from './siwx/siwTezos.js' // 5 minute default clockskew @@ -24,7 +25,7 @@ export type Header = { } export type Signature = { - t: 'eip191' | 'eip1271' | 'solana:ed25519' | 'tezos:ed25519' + t: 'eip191' | 'eip1271' | 'solana:ed25519' | 'tezos:ed25519' | 'sui:ed25519' s: string } export type Cacao = { @@ -212,6 +213,51 @@ export namespace Cacao { return cacao } + export function fromSiwSuiMessage(siwSuiMessage: SiwSuiMessage): Cacao { + const cacao: Cacao = { + h: { + t: 'caip122', + }, + p: { + domain: siwSuiMessage.domain, + iat: siwSuiMessage.issuedAt, + iss: `did:pkh:sui:${siwSuiMessage.chainId}:${siwSuiMessage.address}`, + aud: siwSuiMessage.uri, + version: siwSuiMessage.version, + nonce: siwSuiMessage.nonce, + }, + } + + if (siwSuiMessage.signature) { + cacao.s = { + t: 'sui:ed25519', + s: siwSuiMessage.signature, + } + } + + if (siwSuiMessage.notBefore) { + cacao.p.nbf = siwSuiMessage.notBefore + } + + if (siwSuiMessage.expirationTime) { + cacao.p.exp = siwSuiMessage.expirationTime + } + + if (siwSuiMessage.statement) { + cacao.p.statement = siwSuiMessage.statement + } + + if (siwSuiMessage.requestId) { + cacao.p.requestId = siwSuiMessage.requestId + } + + if (siwSuiMessage.resources) { + cacao.p.resources = siwSuiMessage.resources + } + + return cacao + } + export function fromSiwTezosMessage(siwTezosMessage: SiwTezosMessage): Cacao { const cacao: Cacao = { h: { diff --git a/packages/cacao/src/index.ts b/packages/cacao/src/index.ts index 9596ed8a..e0a43b57 100644 --- a/packages/cacao/src/index.ts +++ b/packages/cacao/src/index.ts @@ -1,5 +1,6 @@ export * from './siwx/siwe.js' export * from './siwx/siws.js' export * from './siwx/siwx.js' +export * from './siwx/siwSui.js' export * from './siwx/siwTezos.js' export * from './cacao.js' diff --git a/packages/cacao/src/siwx/siwSui.ts b/packages/cacao/src/siwx/siwSui.ts new file mode 100644 index 00000000..393681c0 --- /dev/null +++ b/packages/cacao/src/siwx/siwSui.ts @@ -0,0 +1,25 @@ +import { SignatureType, SiwxMessage } from './siwx.js' + + +export class SiwsSuiMessage extends SiwxMessage { + toMessage(): string { + return super.toMessage('Sui') + } + + signMessage(): Uint8Array { + let message: Uint8Array + switch (this.type) { + case SignatureType.PERSONAL_SIGNATURE: { + message = new TextEncoder().encode(this.toMessage()) + break + } + + default: { + message = new TextEncoder().encode(this.toMessage()) + break + } + } + + return message + } +} \ No newline at end of file From ccbafe41e764c93c10eec9f53fedaad595b357d1 Mon Sep 17 00:00:00 2001 From: 0xzoz Date: Tue, 10 Jan 2023 14:19:15 -0800 Subject: [PATCH 2/7] feat: add sui auth --- packages/pkh-sui/.eslintrc.json | 13 +++++ packages/pkh-sui/jest.config.json | 22 +++++++++ packages/pkh-sui/package.json | 53 ++++++++++++++++++++ packages/pkh-sui/src/authmethod.ts | 77 +++++++++++++++++++++++++++++ packages/pkh-sui/src/index.ts | 1 + packages/pkh-sui/tsconfig.json | 7 +++ packages/pkh-sui/tsconfig.lint.json | 4 ++ 7 files changed, 177 insertions(+) create mode 100644 packages/pkh-sui/.eslintrc.json create mode 100644 packages/pkh-sui/jest.config.json create mode 100644 packages/pkh-sui/package.json create mode 100644 packages/pkh-sui/src/authmethod.ts create mode 100644 packages/pkh-sui/src/index.ts create mode 100644 packages/pkh-sui/tsconfig.json create mode 100644 packages/pkh-sui/tsconfig.lint.json diff --git a/packages/pkh-sui/.eslintrc.json b/packages/pkh-sui/.eslintrc.json new file mode 100644 index 00000000..ff3ed28e --- /dev/null +++ b/packages/pkh-sui/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "extends": ["3box", "3box/jest", "3box/typescript"], + "parserOptions": { + "project": ["tsconfig.lint.json"] + }, + "rules": { + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/ban-ts-ignore": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-unsafe-assignment": "off" + } + } \ No newline at end of file diff --git a/packages/pkh-sui/jest.config.json b/packages/pkh-sui/jest.config.json new file mode 100644 index 00000000..9b3ecc28 --- /dev/null +++ b/packages/pkh-sui/jest.config.json @@ -0,0 +1,22 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "testRegex": ".(spec|test).ts$", + "testEnvironment": "node", + "extensionsToTreatAsEsm": [".ts"], + "globals": { + "ts-jest": { + "useESM": true + } + }, + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.js$": "$1" + }, + "transform": { + "^.+\\.(t|j)s$": [ + "@swc/jest", + { + "root": "../.." + } + ] + } + } \ No newline at end of file diff --git a/packages/pkh-sui/package.json b/packages/pkh-sui/package.json new file mode 100644 index 00000000..a24389a3 --- /dev/null +++ b/packages/pkh-sui/package.json @@ -0,0 +1,53 @@ +{ + "name": "@didtools/pkh-sui", + "version": "0.0.1", + "license": "(Apache-2.0 OR MIT)", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": "./dist/index.js" + }, + "files": [ + "dist" + ], + "engines": { + "node": ">=14.14" + }, + "sideEffects": false, + "scripts": { + "build:clean": "del dist", + "build:js": "swc src -d ./dist --config-file ../../.swcrc", + "build:types": "tsc --emitDeclarationOnly --skipLibCheck", + "build": "pnpm run build:clean && pnpm run build:types && pnpm run build:js", + "lint": "eslint src --fix", + "test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js", + "test:ci": "pnpm run test --ci --coverage", + "prepare": "pnpm run build", + "prepublishOnly": "package-check", + "size": "./node_modules/.bin/size-limit", + "analyze": "./node_modules/.bin/size-limit --why" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ceramicnetwork/js-did.git" + }, + "keywords": [ + "DID", + "identity", + "did-provider", + "self-sovereign" + ], + "bugs": { + "url": "https://github.com/ceramicnetwork/js-did/issues" + }, + "homepage": "https://github.com/ceramicnetwork/js-did#readme", + "devDependencies": { + "typescript": "^4.5.4" + }, + "dependencies": { + "@didtools/cacao": "workspace:^1.0.0", + "@stablelib/random": "^1.0.2", + "caip": "^1.1.0" + } + } \ No newline at end of file diff --git a/packages/pkh-sui/src/authmethod.ts b/packages/pkh-sui/src/authmethod.ts new file mode 100644 index 00000000..dfe63317 --- /dev/null +++ b/packages/pkh-sui/src/authmethod.ts @@ -0,0 +1,77 @@ +import { AccountId } from 'caip' +import { randomString } from '@stablelib/random' +import { Cacao, SiwSuiMessage, AuthMethod, AuthMethodOpts } from '@didtools/cacao' + +export const SUI_MAINNET_CHAIN_REF = 'mainnet' // TBD when CAIP-2 is finalized +export const VERSION = '1' +export const CHAIN_NAMESPACE = 'sui' + +export namespace SuiWebAuth { + // eslint-disable-next-line @typescript-eslint/require-await + export async function getAuthMethod(suiProvider: any, account: AccountId): Promise { + if (typeof window === 'undefined') + throw new Error('Web Auth method requires browser environment') + const domain = (window as Window).location.hostname + + return async (opts: AuthMethodOpts): Promise => { + opts.domain = domain + return createCACAO(opts, suiProvider, account) + } + } +} + +export type SupportedProvider = { + signMessage: (message: Uint8Array) => Promise<{ signature: Uint8Array }> +} + +export function assertSupportedProvider(suiProvider: any): asserts suiProvider is SupportedProvider { + const p = suiProvider as SupportedProvider + if (p.signMessage == null) { + throw new Error('Unsupported provider; provider must implement requestSignPayload') + } +} + +async function sign(suiProvider: any, message: string) { + assertSupportedProvider(suiProvider) + const { signature } = await suiProvider.signMessage(message) + return signature +} + +async function createCACAO( + opts: AuthMethodOpts, + suiProvider: any, + account: AccountId +): Promise { + const now = new Date() + const oneWeekLater = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000) + + const siwSuiMessage = new SiwSuiMessage({ + domain: opts.domain, + address: account.address, + statement: opts.statement ?? 'Give this application access to some of your data on Ceramic', + uri: opts.uri, + version: VERSION, + nonce: opts.nonce ?? randomString(10), + issuedAt: now.toISOString(), + expirationTime: opts.expirationTime ?? oneWeekLater.toISOString(), + chainId: account.chainId.reference, + resources: opts.resources, + }) + + const signData = siwSuiMessage.signMessage() + const signature = await sign(suiProvider, signData) + siwSuiMessage.signature = signature + return Cacao.fromSiwSuiMessage(siwSuiMessage) +} + +export async function getAccountId(suiProvider: any, address: string): Promise { + const suiChainId = await requestChainId(suiProvider) + const chainId = `${CHAIN_NAMESPACE}:${suiChainId}` + return new AccountId({ address, chainId }) +} + +// eslint-disable-next-line @typescript-eslint/require-await +export async function requestChainId(_suiProvider: any): Promise { + // TODO: add testnets + return SUI_MAINNET_CHAIN_REF +} \ No newline at end of file diff --git a/packages/pkh-sui/src/index.ts b/packages/pkh-sui/src/index.ts new file mode 100644 index 00000000..ef565dfa --- /dev/null +++ b/packages/pkh-sui/src/index.ts @@ -0,0 +1 @@ +import * from './authmethod.js' \ No newline at end of file diff --git a/packages/pkh-sui/tsconfig.json b/packages/pkh-sui/tsconfig.json new file mode 100644 index 00000000..b2e96e01 --- /dev/null +++ b/packages/pkh-sui/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src"] + } \ No newline at end of file diff --git a/packages/pkh-sui/tsconfig.lint.json b/packages/pkh-sui/tsconfig.lint.json new file mode 100644 index 00000000..7484be4b --- /dev/null +++ b/packages/pkh-sui/tsconfig.lint.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"] + } \ No newline at end of file From 11ba379a0a39374f726c3862d125da7cab2a050d Mon Sep 17 00:00:00 2001 From: 0xzoz Date: Tue, 10 Jan 2023 15:32:37 -0800 Subject: [PATCH 3/7] feat: add sui verifier and chain references --- packages/pkh-sui/src/authmethod.ts | 5 +- packages/pkh-sui/src/index.ts | 108 +++++++++++++++++++++++++++- packages/pkh-sui/src/verifier.ts | 40 +++++++++++ packages/pkh-sui/test/index.test.ts | 5 ++ 4 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 packages/pkh-sui/src/verifier.ts create mode 100644 packages/pkh-sui/test/index.test.ts diff --git a/packages/pkh-sui/src/authmethod.ts b/packages/pkh-sui/src/authmethod.ts index dfe63317..00c44168 100644 --- a/packages/pkh-sui/src/authmethod.ts +++ b/packages/pkh-sui/src/authmethod.ts @@ -3,6 +3,9 @@ import { randomString } from '@stablelib/random' import { Cacao, SiwSuiMessage, AuthMethod, AuthMethodOpts } from '@didtools/cacao' export const SUI_MAINNET_CHAIN_REF = 'mainnet' // TBD when CAIP-2 is finalized +export const SUI_DEVNET_CHAIN_REF = 'devnet'; +export const SUI_TESTNET_CHAIN_REF = 'testnet'; +export const SUI_LOCALNET_CHAIN_REF = 'localnet'; export const VERSION = '1' export const CHAIN_NAMESPACE = 'sui' @@ -31,7 +34,7 @@ export function assertSupportedProvider(suiProvider: any): asserts suiProvider i } } -async function sign(suiProvider: any, message: string) { +async function sign(suiProvider: any, message: Uint8Array) { assertSupportedProvider(suiProvider) const { signature } = await suiProvider.signMessage(message) return signature diff --git a/packages/pkh-sui/src/index.ts b/packages/pkh-sui/src/index.ts index ef565dfa..10439098 100644 --- a/packages/pkh-sui/src/index.ts +++ b/packages/pkh-sui/src/index.ts @@ -1 +1,107 @@ -import * from './authmethod.js' \ No newline at end of file +/** + * # Sui AuthMethod and Verifier + * Implements support to authenticate, authorize and verify with Sui accounts as a did:pkh with SIWS(X) and CACAO. + * Primarly used with `did-session` and `@didtools/cacao`. + * + * ## Installation + * + * ``` + * npm install --save @didtools/pkh-solana + * ``` + * + * ## Auth Usage + * + * To Auth in web based env, use any injected Sui provider that implements the sui wallet standards interface with `SuiWebAuth`. see https://github.com/MystenLabs/sui/tree/main/sdk/wallet-adapter/wallet-standard and https://github.com/suiet/wallet-kit + * + * ```js + * // Web Auth Usage + * import { SuiWebAuth, getAccountIdByNetwork } from '@didtools/pkh-sui' + * // ... + * + * const suiProvider = // import/get your Solana provider (ie: window.phantom.solana) + * const address = await suiProvider.connect().address + * const accountId = getAccountIdByNetwork('mainnet', address.publicKey.toString()) + * + * const authMethod = await SolanaWebAuth.getAuthMethod(solProvider, accountId) + * ``` + * + * To Auth in a Node based env, use any standard Solana provider interface with `SolanaNodeAuth` + * + * + * ```js + * // Node Auth Usage + * import { SolanaNodeAuth, getAccountIdByNetwork } from '@didtools/pkh-solana' + * // ... + * + * const solProvider = // import/get your Solana provider (ie: window.phantom.solana) + * const address = await solProvider.connect() + * const accountId = getAccountIdByNetwork('mainnet', address.publicKey.toString()) + * const appName = 'MyNodeApp' + * + * const authMethod = await SuiWebAuth.getAuthMethod(suiProvider, accountId, appName) + * ``` + * + * To use with did-session and reference did-session docs for more details. + * + * ```js + * const client = new ComposeClient({ceramic, definition}) + * const resources = client.resources + * + * const session = await DIDSession.authorize(authMethod, { resources }) + * client.setDID(session.did) + * ``` + * + * ## Configuration + * + * AuthMethod creators consume a standard Sui provider and an AccountId. AccountID follows the + * CAIP10 standard. The helper methods `getAccountIdByNetwork` and `getAccountId` are provided, but you can also create an AccountID + * using the CAIP library directly. + * + * ```js + * import { AccountId } from 'caip' + * import { getAccountIdByNetwork, getAccountId } from '@didtools/pkh-sui' + * + * // Using network string + * const accountId = getAccountIdByNetwork('mainnet', address) + * + * // With CAIP + * const suiMainnetChainId = 'mainnet' + * const chainNameSpace = 'sui' + * const chainId = `${chainNameSpace}:${ethMainnetChainId}` + * const accountIdCAIP = new AccountId({ address, chainId }) + * + * // Using Solana Connection to query connect network/chain + * const connection = new Connection(solanaWeb3.clusterApiUrl("mainnet-beta")) + * const accountIdByConnection = await getAccountIdSolana(connection, address) + * + * // accountId = accountIdCAIP = accountIdByConnection + * ``` + * + * + * ## Verifier Usage + * + * Verifiers are needed to verify different did:pkh signed payloads using CACAO. Libraries that need them will + * consume a verifiers map allowing your to register the verifiers you want to support. + * + * ```js + * import { Cacao } from '@didtools/cacao' + * import { getSuiVerifier } from '@didtools/pkh-sui' + * import { DID } from 'dids' + * + * const verifiers = { + * ...getSuierifier() + * } + * + * // Directly with cacao + * Cacao.verify(cacao, { verifiers, ...opts}) + * + * // With DIDS, reference DIDS for more details + * const dids = //configured dids instance + * await dids.verifyJWS(jws, { capability, verifiers, ...opts}) + * ``` + * + * @module pkh-sui + */ + +import * from './authmethod.js' +import * from './verifier.js' \ No newline at end of file diff --git a/packages/pkh-sui/src/verifier.ts b/packages/pkh-sui/src/verifier.ts new file mode 100644 index 00000000..a8309ced --- /dev/null +++ b/packages/pkh-sui/src/verifier.ts @@ -0,0 +1,40 @@ +import { + SiwSuiMessage, + Cacao, + VerifyOptions, + verifyTimeChecks, + assertSigned, + Verifiers, + } from '@didtools/cacao' + import { AccountId } from 'caip' + import nacl from 'tweetnacl'; + import { fromString as u8aFromString } from 'uint8arrays/from-string' + + /** + * Get a configured CACAO SuiVerifier map for Sui accounts + */ + export function getSuiVerifier(): Verifiers { + return { + // eslint-disable-next-line @typescript-eslint/require-await + 'sui:ed25519': async (cacao: Cacao, opts: VerifyOptions): Promise => { + verifySuiSignature(cacao, opts) + }, + } + } + + export function verifySuiSignature(cacao: Cacao, options: VerifyOptions) { + assertSigned(cacao) + verifyTimeChecks(cacao, options) + + const msg = SiwSuiMessage.fromCacao(cacao) + const sig = cacao.s.s + + const messageU8 = msg.signMessage() + const sigU8 = u8aFromString(sig, 'base64') + const issAddress = AccountId.parse(cacao.p.iss.replace('did:pkh:', '')).address + const pubKeyU8 = u8aFromString(issAddress, 'base64') + + if (!nacl.sign.detached.verify(messageU8, sigU8, pubKeyU8)) { + throw new Error(`Signature does not belong to issuer`) + } + } \ No newline at end of file diff --git a/packages/pkh-sui/test/index.test.ts b/packages/pkh-sui/test/index.test.ts new file mode 100644 index 00000000..60fc0433 --- /dev/null +++ b/packages/pkh-sui/test/index.test.ts @@ -0,0 +1,5 @@ +describe('Sui Cacao Auth Verify', () => { + test.skip('Verify', () => { + + }) + }) \ No newline at end of file From 9c36e8ca687101aee01bdad3c4984da1a94df757 Mon Sep 17 00:00:00 2001 From: 0xzoz Date: Tue, 10 Jan 2023 15:36:31 -0800 Subject: [PATCH 4/7] feat: add readme --- packages/pkh-sui/README.md | 105 +++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 packages/pkh-sui/README.md diff --git a/packages/pkh-sui/README.md b/packages/pkh-sui/README.md new file mode 100644 index 00000000..ee866393 --- /dev/null +++ b/packages/pkh-sui/README.md @@ -0,0 +1,105 @@ + # Sui AuthMethod and Verifier + Implements support to authenticate, authorize and verify with Sui accounts as a did:pkh with SIWS(X) and CACAO. + + Primarly used with `did-session` and `@didtools/cacao`. + + ## Installation + +``` +npm install --save @didtools/pkh-solana +``` + +## Auth Usage + +To Auth in web based env, use any injected Sui provider that implements the sui wallet standards interface with `SuiWebAuth`. see https://github.com/MystenLabs/sui/tree/main/sdk/wallet-adapter/wallet-standard and https://github.com/suiet/wallet-kit + +```js +// Web Auth Usage +import { SuiWebAuth, getAccountIdByNetwork } from '@didtools/pkh-sui' +// ... + +const suiProvider = // import/get your Solana provider (ie: window.phantom.solana) +const address = await suiProvider.connect().address +const accountId = getAccountIdByNetwork('mainnet', address.publicKey.toString()) + +const authMethod = await SolanaWebAuth.getAuthMethod(solProvider, accountId) +``` + +To Auth in a Node based env, use any standard Solana provider interface with `SolanaNodeAuth` + + +```js +// Node Auth Usage +import { SolanaNodeAuth, getAccountIdByNetwork } from '@didtools/pkh-solana' +// ... + +const solProvider = // import/get your Solana provider (ie: window.phantom.solana) +const address = await solProvider.connect() +const accountId = getAccountIdByNetwork('mainnet', address.publicKey.toString()) +const appName = 'MyNodeApp' + +const authMethod = await SuiWebAuth.getAuthMethod(suiProvider, accountId, appName) +``` + +To use with did-session and reference did-session docs for more details. + +```js +const client = new ComposeClient({ceramic, definition}) +const resources = client.resources + +const session = await DIDSession.authorize(authMethod, { resources }) +client.setDID(session.did) +``` + +## Configuration + +AuthMethod creators consume a standard Sui provider and an AccountId. AccountID follows the +CAIP10 standard. The helper methods `getAccountIdByNetwork` and `getAccountId` are provided, but you can also create an AccountID +using the CAIP library directly. + +```js +import { AccountId } from 'caip' +import { getAccountIdByNetwork, getAccountId } from '@didtools/pkh-sui' + +// Using network string +const accountId = getAccountIdByNetwork('mainnet', address) + +// With CAIP +const suiMainnetChainId = 'mainnet' +const chainNameSpace = 'sui' +const chainId = `${chainNameSpace}:${ethMainnetChainId}` +const accountIdCAIP = new AccountId({ address, chainId }) + +// Using Solana Connection to query connect network/chain +const connection = new Connection(solanaWeb3.clusterApiUrl("mainnet-beta")) +const accountIdByConnection = await getAccountIdSolana(connection, address) + +// accountId = accountIdCAIP = accountIdByConnection +``` + + +## Verifier Usage + +Verifiers are needed to verify different did:pkh signed payloads using CACAO. Libraries that need them will +consume a verifiers map allowing your to register the verifiers you want to support. + +```js +import { Cacao } from '@didtools/cacao' +import { getSuiVerifier } from '@didtools/pkh-sui' +import { DID } from 'dids' + +const verifiers = { +...getSuierifier() +} + +// Directly with cacao +Cacao.verify(cacao, { verifiers, ...opts}) + +// With DIDS, reference DIDS for more details +const dids = //configured dids instance +await dids.verifyJWS(jws, { capability, verifiers, ...opts}) +``` + +## License + +Apache-2.0 OR MIT From 56fcbb2faf929efd0b482ce330903b1ea03f9305 Mon Sep 17 00:00:00 2001 From: 0xzoz Date: Fri, 13 Jan 2023 10:14:57 -0800 Subject: [PATCH 5/7] fix build issues --- packages/cacao/src/siwx/siwSui.ts | 2 +- packages/pkh-sui/package.json | 108 +++++++++++++++-------------- packages/pkh-sui/src/authmethod.ts | 3 +- packages/pkh-sui/src/index.ts | 4 +- pnpm-lock.yaml | 19 +++++ 5 files changed, 80 insertions(+), 56 deletions(-) diff --git a/packages/cacao/src/siwx/siwSui.ts b/packages/cacao/src/siwx/siwSui.ts index 393681c0..f5be5a0d 100644 --- a/packages/cacao/src/siwx/siwSui.ts +++ b/packages/cacao/src/siwx/siwSui.ts @@ -1,7 +1,7 @@ import { SignatureType, SiwxMessage } from './siwx.js' -export class SiwsSuiMessage extends SiwxMessage { +export class SiwSuiMessage extends SiwxMessage { toMessage(): string { return super.toMessage('Sui') } diff --git a/packages/pkh-sui/package.json b/packages/pkh-sui/package.json index a24389a3..bc0a84e3 100644 --- a/packages/pkh-sui/package.json +++ b/packages/pkh-sui/package.json @@ -1,53 +1,57 @@ { - "name": "@didtools/pkh-sui", - "version": "0.0.1", - "license": "(Apache-2.0 OR MIT)", - "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "exports": { - ".": "./dist/index.js" - }, - "files": [ - "dist" - ], - "engines": { - "node": ">=14.14" - }, - "sideEffects": false, - "scripts": { - "build:clean": "del dist", - "build:js": "swc src -d ./dist --config-file ../../.swcrc", - "build:types": "tsc --emitDeclarationOnly --skipLibCheck", - "build": "pnpm run build:clean && pnpm run build:types && pnpm run build:js", - "lint": "eslint src --fix", - "test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js", - "test:ci": "pnpm run test --ci --coverage", - "prepare": "pnpm run build", - "prepublishOnly": "package-check", - "size": "./node_modules/.bin/size-limit", - "analyze": "./node_modules/.bin/size-limit --why" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/ceramicnetwork/js-did.git" - }, - "keywords": [ - "DID", - "identity", - "did-provider", - "self-sovereign" - ], - "bugs": { - "url": "https://github.com/ceramicnetwork/js-did/issues" - }, - "homepage": "https://github.com/ceramicnetwork/js-did#readme", - "devDependencies": { - "typescript": "^4.5.4" - }, - "dependencies": { - "@didtools/cacao": "workspace:^1.0.0", - "@stablelib/random": "^1.0.2", - "caip": "^1.1.0" - } - } \ No newline at end of file + "name": "@didtools/pkh-sui", + "version": "0.0.4", + "author": "3Box Labs", + "license": "(Apache-2.0 OR MIT)", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": "./dist/index.js" + }, + "files": [ + "dist" + ], + "engines": { + "node": ">=14.14" + }, + "sideEffects": false, + "scripts": { + "build:clean": "del dist", + "build:js": "swc src -d ./dist --config-file ../../.swcrc", + "build:types": "tsc --emitDeclarationOnly --skipLibCheck", + "build": "pnpm run build:clean && pnpm run build:types && pnpm run build:js", + "lint": "eslint src --fix", + "test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js", + "test:ci": "pnpm run test --ci --coverage", + "prepare": "pnpm run build", + "prepublishOnly": "package-check", + "size": "./node_modules/.bin/size-limit", + "analyze": "./node_modules/.bin/size-limit --why" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ceramicnetwork/js-did.git" + }, + "keywords": [ + "DID", + "identity", + "did-provider", + "self-sovereign" + ], + "bugs": { + "url": "https://github.com/ceramicnetwork/js-did/issues" + }, + "homepage": "https://github.com/ceramicnetwork/js-did#readme", + "devDependencies": { + "typescript": "^4.5.4" + }, + "dependencies": { + "@didtools/cacao": "workspace:^1.1.0", + "@stablelib/ed25519": "^1.0.3", + "@stablelib/random": "^1.0.2", + "caip": "^1.1.0", + "tweetnacl": "^1.0.3", + "uint8arrays": "^3.1.0" + } +} \ No newline at end of file diff --git a/packages/pkh-sui/src/authmethod.ts b/packages/pkh-sui/src/authmethod.ts index 00c44168..95758f91 100644 --- a/packages/pkh-sui/src/authmethod.ts +++ b/packages/pkh-sui/src/authmethod.ts @@ -1,5 +1,6 @@ import { AccountId } from 'caip' import { randomString } from '@stablelib/random' +import { toString } from 'uint8arrays/to-string' import { Cacao, SiwSuiMessage, AuthMethod, AuthMethodOpts } from '@didtools/cacao' export const SUI_MAINNET_CHAIN_REF = 'mainnet' // TBD when CAIP-2 is finalized @@ -37,7 +38,7 @@ export function assertSupportedProvider(suiProvider: any): asserts suiProvider i async function sign(suiProvider: any, message: Uint8Array) { assertSupportedProvider(suiProvider) const { signature } = await suiProvider.signMessage(message) - return signature + return toString(signature, 'base64') } async function createCACAO( diff --git a/packages/pkh-sui/src/index.ts b/packages/pkh-sui/src/index.ts index 10439098..efbfabb1 100644 --- a/packages/pkh-sui/src/index.ts +++ b/packages/pkh-sui/src/index.ts @@ -103,5 +103,5 @@ * @module pkh-sui */ -import * from './authmethod.js' -import * from './verifier.js' \ No newline at end of file +export * from './authmethod.js' +export * from './verifier.js' \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45e82a2f..d9a32622 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -279,6 +279,25 @@ importers: devDependencies: typescript: 4.8.4 + packages/pkh-sui: + specifiers: + '@didtools/cacao': workspace:^1.1.0 + '@stablelib/ed25519': ^1.0.3 + '@stablelib/random': ^1.0.2 + caip: ^1.1.0 + tweetnacl: ^1.0.3 + typescript: ^4.5.4 + uint8arrays: ^3.1.0 + dependencies: + '@didtools/cacao': link:../cacao + '@stablelib/ed25519': 1.0.3 + '@stablelib/random': 1.0.2 + caip: 1.1.0 + tweetnacl: 1.0.3 + uint8arrays: 3.1.1 + devDependencies: + typescript: 4.8.4 + packages/pkh-tezos: specifiers: '@didtools/cacao': workspace:^1.1.0 From 8548ebe220b0835528c90bd197d8ca9c05589806 Mon Sep 17 00:00:00 2001 From: 0xzoz Date: Fri, 13 Jan 2023 11:36:08 -0800 Subject: [PATCH 6/7] add docs and outstanding functions --- docs/modules/pkh_sui.SuiWebAuth.md | 20 +++ docs/modules/pkh_sui.md | 249 +++++++++++++++++++++++++++++ packages/pkh-sui/README.md | 27 +--- packages/pkh-sui/src/authmethod.ts | 18 ++- 4 files changed, 287 insertions(+), 27 deletions(-) create mode 100644 docs/modules/pkh_sui.SuiWebAuth.md create mode 100644 docs/modules/pkh_sui.md diff --git a/docs/modules/pkh_sui.SuiWebAuth.md b/docs/modules/pkh_sui.SuiWebAuth.md new file mode 100644 index 00000000..aa2b397a --- /dev/null +++ b/docs/modules/pkh_sui.SuiWebAuth.md @@ -0,0 +1,20 @@ +# Namespace: SuiWebAuth + +[pkh-sui](pkh_sui.md).SuiWebAuth + +## Functions + +### getAuthMethod + +▸ **getAuthMethod**(`suiProvider`, `account`): `Promise`<`AuthMethod`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `suiProvider` | `any` | +| `account` | `AccountId` | + +#### Returns + +`Promise`<`AuthMethod`\> diff --git a/docs/modules/pkh_sui.md b/docs/modules/pkh_sui.md new file mode 100644 index 00000000..fdb53bcb --- /dev/null +++ b/docs/modules/pkh_sui.md @@ -0,0 +1,249 @@ +# Module: pkh-sui + +## Sui AuthMethod and Verifier +Implements support to authenticate, authorize and verify with Sui accounts as a did:pkh with SIWE(X) and CACAO. +Primarly used with `did-session` and `@didtools/cacao`. + + ## Installation + +``` +npm install --save @didtools/pkh-solana +``` + +## Auth Usage + +To Auth in web based env, use any injected Sui provider that implements the sui wallet standards interface with `SuiWebAuth`. see https://github.com/MystenLabs/sui/tree/main/sdk/wallet-adapter/wallet-standard and https://github.com/suiet/wallet-kit + +```js +// Web Auth Usage +import { SuiWebAuth, getAccountIdByNetwork } from '@didtools/pkh-sui' +// ... + +const suiProvider = // import/get your Solana provider (ie: window.phantom.solana) +const address = await suiProvider.connect().address +const accountId = getAccountIdByNetwork('mainnet', address.publicKey.toString()) + +const authMethod = await SuiWebAuth.getAuthMethod(suiProvider, accountId) +``` + +## Configuration + +AuthMethod creators consume a standard Sui provider and an AccountId. AccountID follows the +CAIP10 standard. The helper methods `getAccountIdByNetwork` and `getAccountId` are provided, but you can also create an AccountID +using the CAIP library directly. + +```js +import { AccountId } from 'caip' +import { getAccountIdByNetwork, getAccountId } from '@didtools/pkh-sui' + +// Using network string +const accountId = getAccountIdByNetwork('mainnet', address) + +// With CAIP +const suiMainnetChainId = 'mainnet' +const chainNameSpace = 'sui' +const chainId = `${chainNameSpace}:${ethMainnetChainId}` +const accountIdCAIP = new AccountId({ address, chainId }) + +// Using Solana Connection to query connect network/chain +const connection = new Connection(solanaWeb3.clusterApiUrl("mainnet-beta")) +const accountIdByConnection = await getAccountIdSolana(connection, address) + +// accountId = accountIdCAIP = accountIdByConnection +``` + + +## Verifier Usage + +Verifiers are needed to verify different did:pkh signed payloads using CACAO. Libraries that need them will +consume a verifiers map allowing your to register the verifiers you want to support. + +```js +import { Cacao } from '@didtools/cacao' +import { getSuiVerifier } from '@didtools/pkh-sui' +import { DID } from 'dids' + +const verifiers = { +...getSuierifier() +} + +// Directly with cacao +Cacao.verify(cacao, { verifiers, ...opts}) + +// With DIDS, reference DIDS for more details +const dids = //configured dids instance +await dids.verifyJWS(jws, { capability, verifiers, ...opts}) +``` + +## Namespaces + +- [SuiWebAuth](pkh_sui.SuiWebAuth.md) + +## Type Aliases + +### SupportedProvider + +Ƭ **SupportedProvider**: `Object` + +#### Type declaration + +| Name | Type | +| :------ | :------ | +| `signMessage` | (`message`: `Uint8Array`, `type`: `string`) => `Promise`<{ `signature`: `Uint8Array` }\> | + +## Variables + +### CHAIN\_NAMESPACE + +• `Const` **CHAIN\_NAMESPACE**: ``"sui"`` + +___ + +### SUI\_DEVNET\_CHAIN\_REF + +• `Const` **SUI\_DEVNET\_CHAIN\_REF**: ``"devnet"`` + +___ + +### SUI\_TESTNET\_CHAIN\_REF + +• `Const` **SUI\_TESTNET\_CHAIN\_REF**: ``"testnet"`` + +___ + +### SUI\_MAINNET\_CHAIN\_REF + +• `Const` **SUI\_MAINNET\_CHAIN\_REF**: ``"mainnet"`` + +___ + +### VERSION + +• `Const` **VERSION**: ``"1"`` + +___ + +### chainIdMap + +• `Const` **chainIdMap**: `Object` + +#### Type declaration + +| Name | Type | +| :------ | :------ | +| `devnet` | `string` | +| `testnet` | `string` | +| `mainnet` | `string` | + +## Functions + +### assertSupportedConnection + +▸ **assertSupportedConnection**(`suiProvider`): asserts suiProvider is SupportedProvider + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `suiProvider` | `any` | + +#### Returns + +asserts suiProvider is SupportedProvider + +___ + +### assertSupportedProvider + +▸ **assertSupportedProvider**(`suiProvider`): asserts suiProvider is SupportedProvider + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `suiProvider` | `any` | + +#### Returns + +asserts suiProvider is SupportedProvider + +___ + +### getAccountId + +▸ **getAccountId**(`suiProvider`, `address`): `Promise`<`AccountId`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `suiProvider` | `any` | +| `address` | `string` | + +#### Returns + +`Promise`<`AccountId`\> + +___ + +### getAccountIdByNetwork + +▸ **getAccountIdByNetwork**(`network`, `address`): `AccountId` + +Helper function to get an accountId (CAIP10) for an Sui account by network string 'mainet' | 'testnet' | 'devenet' + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `network` | `SuiNetwork` | +| `address` | `string` | + +#### Returns + +`AccountId` + +___ + +### getSuiVerifier + +▸ **getSuiVerifier**(): `Verifiers` + +Get a configured CACAO SuiVerifier map for Sui accounts + +#### Returns + +`Verifiers` + +___ + +### requestChainId + +▸ **requestChainId**(`suiConnection`): `Promise`<`string`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `suiConnection` | `any` | + +#### Returns + +`Promise`<`string`\> + +___ + +### verifySuiSignature + +▸ **verifySuiSignature**(`cacao`, `options`): `void` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `cacao` | `Cacao` | +| `options` | `VerifyOptions` | + +#### Returns + +`void` diff --git a/packages/pkh-sui/README.md b/packages/pkh-sui/README.md index ee866393..1e3e16a5 100644 --- a/packages/pkh-sui/README.md +++ b/packages/pkh-sui/README.md @@ -22,34 +22,9 @@ const suiProvider = // import/get your Solana provider (ie: window.phantom.solan const address = await suiProvider.connect().address const accountId = getAccountIdByNetwork('mainnet', address.publicKey.toString()) -const authMethod = await SolanaWebAuth.getAuthMethod(solProvider, accountId) +const authMethod = await SuiWebAuth.getAuthMethod(suiProvider, accountId) ``` -To Auth in a Node based env, use any standard Solana provider interface with `SolanaNodeAuth` - - -```js -// Node Auth Usage -import { SolanaNodeAuth, getAccountIdByNetwork } from '@didtools/pkh-solana' -// ... - -const solProvider = // import/get your Solana provider (ie: window.phantom.solana) -const address = await solProvider.connect() -const accountId = getAccountIdByNetwork('mainnet', address.publicKey.toString()) -const appName = 'MyNodeApp' - -const authMethod = await SuiWebAuth.getAuthMethod(suiProvider, accountId, appName) -``` - -To use with did-session and reference did-session docs for more details. - -```js -const client = new ComposeClient({ceramic, definition}) -const resources = client.resources - -const session = await DIDSession.authorize(authMethod, { resources }) -client.setDID(session.did) -``` ## Configuration diff --git a/packages/pkh-sui/src/authmethod.ts b/packages/pkh-sui/src/authmethod.ts index 95758f91..b7769def 100644 --- a/packages/pkh-sui/src/authmethod.ts +++ b/packages/pkh-sui/src/authmethod.ts @@ -6,10 +6,18 @@ import { Cacao, SiwSuiMessage, AuthMethod, AuthMethodOpts } from '@didtools/caca export const SUI_MAINNET_CHAIN_REF = 'mainnet' // TBD when CAIP-2 is finalized export const SUI_DEVNET_CHAIN_REF = 'devnet'; export const SUI_TESTNET_CHAIN_REF = 'testnet'; -export const SUI_LOCALNET_CHAIN_REF = 'localnet'; export const VERSION = '1' export const CHAIN_NAMESPACE = 'sui' + +export const chainIdMap = { + mainnet: SUI_MAINNET_CHAIN_REF, + testnet: SUI_TESTNET_CHAIN_REF, + devnet: SUI_DEVNET_CHAIN_REF, +} + +type SuiNetwork = 'mainnet' | 'testnet' | 'devnet' + export namespace SuiWebAuth { // eslint-disable-next-line @typescript-eslint/require-await export async function getAuthMethod(suiProvider: any, account: AccountId): Promise { @@ -78,4 +86,12 @@ export async function getAccountId(suiProvider: any, address: string): Promise { // TODO: add testnets return SUI_MAINNET_CHAIN_REF +} + +/** + * Helper function to get an accountId (CAIP10) for an Sui account by network string 'mainet' | 'testnet' | 'devenet' + */ +export function getAccountIdByNetwork(network: SuiNetwork, address: string): AccountId { + const chainId = `${CHAIN_NAMESPACE}:${chainIdMap[network]}` + return new AccountId({ address, chainId }) } \ No newline at end of file From 133961757b9959d6cade4f08e5ceb59bab4a27e9 Mon Sep 17 00:00:00 2001 From: 0xzoz Date: Thu, 2 Feb 2023 13:04:38 -0800 Subject: [PATCH 7/7] fix: signing message type --- packages/pkh-sui/src/authmethod.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/pkh-sui/src/authmethod.ts b/packages/pkh-sui/src/authmethod.ts index b7769def..fd02ca50 100644 --- a/packages/pkh-sui/src/authmethod.ts +++ b/packages/pkh-sui/src/authmethod.ts @@ -33,7 +33,12 @@ export namespace SuiWebAuth { } export type SupportedProvider = { - signMessage: (message: Uint8Array) => Promise<{ signature: Uint8Array }> + signMessage: (input: {message: Uint8Array}) => Promise; +} + +export type ExpSignMessageOutput = { + signature: Uint8Array; + signedMessage: Uint8Array; } export function assertSupportedProvider(suiProvider: any): asserts suiProvider is SupportedProvider { @@ -45,7 +50,7 @@ export function assertSupportedProvider(suiProvider: any): asserts suiProvider i async function sign(suiProvider: any, message: Uint8Array) { assertSupportedProvider(suiProvider) - const { signature } = await suiProvider.signMessage(message) + const { signature } = await suiProvider.signMessage({message: message}) return toString(signature, 'base64') }