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/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..f5be5a0d --- /dev/null +++ b/packages/cacao/src/siwx/siwSui.ts @@ -0,0 +1,25 @@ +import { SignatureType, SiwxMessage } from './siwx.js' + + +export class SiwSuiMessage 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 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/README.md b/packages/pkh-sui/README.md new file mode 100644 index 00000000..1e3e16a5 --- /dev/null +++ b/packages/pkh-sui/README.md @@ -0,0 +1,80 @@ + # 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 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}) +``` + +## License + +Apache-2.0 OR MIT 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..bc0a84e3 --- /dev/null +++ b/packages/pkh-sui/package.json @@ -0,0 +1,57 @@ +{ + "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 new file mode 100644 index 00000000..fd02ca50 --- /dev/null +++ b/packages/pkh-sui/src/authmethod.ts @@ -0,0 +1,102 @@ +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 +export const SUI_DEVNET_CHAIN_REF = 'devnet'; +export const SUI_TESTNET_CHAIN_REF = 'testnet'; +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 { + 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: (input: {message: Uint8Array}) => Promise; +} + +export type ExpSignMessageOutput = { + signature: Uint8Array; + signedMessage: 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: Uint8Array) { + assertSupportedProvider(suiProvider) + const { signature } = await suiProvider.signMessage({message: message}) + return toString(signature, 'base64') +} + +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 +} + +/** + * 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 diff --git a/packages/pkh-sui/src/index.ts b/packages/pkh-sui/src/index.ts new file mode 100644 index 00000000..efbfabb1 --- /dev/null +++ b/packages/pkh-sui/src/index.ts @@ -0,0 +1,107 @@ +/** + * # 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 + */ + +export * from './authmethod.js' +export * 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 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 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