From bc330b0ca61d5fca90bd5128bdaa2def62ec50a7 Mon Sep 17 00:00:00 2001 From: Steve Korshakov Date: Fri, 17 Jun 2022 08:15:39 +0400 Subject: [PATCH] ref: extract signature verify, moved documentation --- README.md | 134 +------------------- src/connector/TonhubConnector.ts | 64 +--------- src/connector/crypto.ts | 41 ++++++ src/contracts/extractPublicKeyAndAddress.ts | 21 +++ src/index.ts | 10 +- 5 files changed, 79 insertions(+), 191 deletions(-) create mode 100644 src/connector/crypto.ts create mode 100644 src/contracts/extractPublicKeyAndAddress.ts diff --git a/README.md b/README.md index b8701eb..b4d4338 100644 --- a/README.md +++ b/README.md @@ -3,142 +3,16 @@ Connector for dApps for TON blockchain Supported connectors: -* [Tonhub](https://tonhub.com) and [Tondev](https://test.tonhub.com) +* [Tonhub](https://tonhub.com) and [Sandbox Wallet](https://test.tonhub.com) +* Extension for [Tonhub](https://tonhub.com) and [Sandbox Wallet](https://test.tonhub.com) ``` yarn install ton-x ``` -## Tonhub connector +## Documentation -Connecting app to a wallet: -1) Create session -2) Show user QR Code or link -3) Await session confirmation - -### Create connector - -```typescript -import { TonhubConnector } from 'ton-x'; -const connector = new TonhubConnector({ testnet: true }); -``` - -### Creating session - -```typescript -let session: TonhubCreatedSession = await connector.createNewSession({ - name: 'Your app name', - url: 'Your app url' -}); - -// Session ID, Seed and Auth Link -const sessionId = session.id; -const sessionSeed = session.seed; -const sessionLink = session.link; -``` - -### Await session confirmation - -```typescript -const session: TonhubSessionAwaited = await connector.awaitSessionReady(sessionId, 5 * 60 * 1000); // 5 min timeout - -if (session.state === 'revoked' || session.state === 'expired') { - // Handle revoked or expired session -} else if (session.state === 'ready') { - - // Handle session - const walletConfig: TonhubWalletConfig = session.walletConfig; - - // You need to persist this values to work with this connection: - // * sessionId - // * sessionSeed - // * walletConfig - - // You can check signed wallet config on backend using TonhubConnector.verifyWalletConfig. - // walletConfig is cryptographically signed for specific session and other parameters - // you can safely use it as authentication proof without the need to sign something. - const correctConfig: boolean = TonhubConnector.verifyWalletConfig(sessionId, walletConfig); - - // ... - -} else { - throw new Error('Impossible'); -} -``` - -### Check session validity -After session established it is useful to check from time to time that session wasn't revoked - -```typescript -const session: TonhubSessionState = await connector.getSessionState(sessionId); -``` - -### Request transaction - -```typescript -// Request body -const request: TonhubTransactionRequest = { - seed: sessionSeed, // Session Seed - appPublicKey: walletConfig.appPublicKey, // Wallet's app public key - to: 'EQCkR1cGmnsE45N4K0otPl5EnxnRakmGqeJUNua5fkWhales', // Destination - value: '10000000000', // Amount in nano-tons - timeout: 5 * 60 * 1000, // 5 minut timeout - stateInit: '....', // Optional serialized to base64 string state_init cell - text: 'Hello world', // Optional comment. If no payload specified - sends actual content, if payload is provided this text is used as UI-only hint - payload: '....' // Optional serialized to base64 string payload cell -}; -const response: TonhubTransactionResponse = await connector.requestTransaction(request); -if (response.type === 'rejected') { - // Handle rejection -} else if (response.type === 'expired') { - // Handle expiration -} else if (response.type === 'invalid_session') { - // Handle expired or invalid session -} else if (response.type === 'success') { - // Handle successful transaction - const externalMessage = response.response; // Signed external message that was sent to the network -} else { - throw new Error('Impossible'); -} -``` - -### Request signature -In tonhub user user signs both text and binary payload, but only text is presented to the user. For future compatibility we recommend to prefix your cell binary payload with `0x00000000`. -```typescript - -const payloadToSign = Buffer.concat([Buffer.from([0, 0, 0, 0]), Buffer.from('Some random string')]); -const payload = beginCell() - .storeBuffer(payloadToSign) - .endCell() - .toBoc({idx:false}) - .toString('base64'); -const text = 'Please, sign our terms or service and privacy policy'; - -// Request body -const request: TonhubSignRequest = { - seed: sessionSeed, // Session Seed - appPublicKey: walletConfig.appPublicKey, // Wallet's app public key - timeout: 5 * 60 * 1000, // 5 minut timeout - text: 'Hello world', // Text to sign, presented to the user. - payload: payload // Optional serialized to base64 string payload cell -}; -const response: TonhubSignResponse = await connector.requestSign(request); -if (response.type === 'rejected') { - // Handle rejection -} else if (response.type === 'expired') { - // Handle expiration -} else if (response.type === 'invalid_session') { - // Handle expired or invalid session -} else if (response.type === 'success') { - // Handle successful transaction - const signature = response.signature; - - // You can check signature on the backend with TonhubConnector.verifySignatureResponse - let correctSignature = TonhubConnector.verifySignatureResponse({ signature: signature, config: walletConfig }); -} else { - throw new Error('Impossible'); -} -``` +All documentation moved to https://developers.tonhub.com # License MIT \ No newline at end of file diff --git a/src/connector/TonhubConnector.ts b/src/connector/TonhubConnector.ts index d5a4596..8ec6767 100644 --- a/src/connector/TonhubConnector.ts +++ b/src/connector/TonhubConnector.ts @@ -7,6 +7,8 @@ import { Cell, Address, beginCell, CommentMessage, safeSign, contractAddress, sa import BN from 'bn.js'; import { TonhubHttpTransport } from '../transport/TonhubHttpTransport'; import { WalletV4Source } from "../contracts/WalletV4Source"; +import { extractPublicKeyAndAddress } from "../contracts/extractPublicKeyAndAddress"; +import { verifySignatureResponse } from "./crypto"; const sessionStateCodec = t.union([ t.type({ @@ -168,32 +170,13 @@ function textToCell(src: string) { export class TonhubConnector { - static extractPublicKey(config: { - walletType: string, - walletConfig: string - }) { - // Extract public key and address - let publicKey: Buffer; - let restoredAddress: Address; - if (config.walletType === 'org.ton.wallets.v4') { - let source = WalletV4Source.restore(config.walletConfig); - restoredAddress = contractAddress(source); - publicKey = source.publicKey; - } else { - return null; - } - - // Public key - return { publicKey, address: restoredAddress }; - } - static verifyWalletConfig(session: string, config: TonhubWalletConfig) { // Check address const address = Address.parseFriendly(config.address).address; // Extract public key and address - let extracted = TonhubConnector.extractPublicKey(config); + let extracted = extractPublicKeyAndAddress(config); if (!extracted) { return false; } @@ -225,45 +208,6 @@ export class TonhubConnector { return safeSignVerify(toSign, Buffer.from(config.walletSig, 'base64'), publicKey); } - static verifySignatureResponse(args: { - signature: string, - text?: string | null | undefined, - payload?: string | null | undefined, - config: TonhubWalletConfig - }) { - // Check address - const address = Address.parseFriendly(args.config.address).address; - - // Extract public key and address - let extracted = TonhubConnector.extractPublicKey(args.config); - if (!extracted) { - return false; - } - - // Check address - if (!extracted.address.equals(address)) { - return false; - } - - let publicKey: Buffer = extracted.publicKey; - - // Package - const textCell = new Cell(); - const payloadCell = new Cell(); - if (typeof args.text === 'string') { - new CommentMessage(args.text).writeTo(textCell); - } - - // Check signature - const data = beginCell() - .storeRef(textCell) - .storeRef(payloadCell) - .endCell(); - const signed = safeSignVerify(data, Buffer.from(args.signature, 'base64'), publicKey); - - return signed; - } - readonly network: 'mainnet' | 'sandbox'; readonly transport: Transport; @@ -536,7 +480,7 @@ export class TonhubConnector { const cellRes = Cell.fromBoc(Buffer.from(result.result, 'base64'))[0]; let slice = cellRes.beginParse(); const resSignature = slice.readBuffer(64); - let correct = TonhubConnector.verifySignatureResponse({ signature: resSignature.toString('base64'), config: session.wallet }); + let correct = verifySignatureResponse({ signature: resSignature.toString('base64'), config: session.wallet }); if (correct) { return { type: 'success', signature: resSignature.toString('base64') }; } else { diff --git a/src/connector/crypto.ts b/src/connector/crypto.ts new file mode 100644 index 0000000..d11655d --- /dev/null +++ b/src/connector/crypto.ts @@ -0,0 +1,41 @@ +import { Address, beginCell, Cell, CommentMessage, safeSignVerify } from "ton"; +import { extractPublicKeyAndAddress } from "../contracts/extractPublicKeyAndAddress"; + +export function verifySignatureResponse(args: { + signature: string, + text?: string | null | undefined, + payload?: string | null | undefined, + config: { address: string, walletType: string, walletConfig: string } +}) { + // Check address + const address = Address.parseFriendly(args.config.address).address; + + // Extract public key and address + let extracted = extractPublicKeyAndAddress(args.config); + if (!extracted) { + return false; + } + + // Check address + if (!extracted.address.equals(address)) { + return false; + } + + let publicKey: Buffer = extracted.publicKey; + + // Package + const textCell = new Cell(); + const payloadCell = new Cell(); + if (typeof args.text === 'string') { + new CommentMessage(args.text).writeTo(textCell); + } + + // Check signature + const data = beginCell() + .storeRef(textCell) + .storeRef(payloadCell) + .endCell(); + const signed = safeSignVerify(data, Buffer.from(args.signature, 'base64'), publicKey); + + return signed; +} \ No newline at end of file diff --git a/src/contracts/extractPublicKeyAndAddress.ts b/src/contracts/extractPublicKeyAndAddress.ts new file mode 100644 index 0000000..aafbbc9 --- /dev/null +++ b/src/contracts/extractPublicKeyAndAddress.ts @@ -0,0 +1,21 @@ +import { Address, contractAddress } from "ton"; +import { WalletV4Source } from "./WalletV4Source"; + +export function extractPublicKeyAndAddress(config: { + walletType: string, + walletConfig: string +}) { + // Extract public key and address + let publicKey: Buffer; + let restoredAddress: Address; + if (config.walletType === 'org.ton.wallets.v4') { + let source = WalletV4Source.restore(config.walletConfig); + restoredAddress = contractAddress(source); + publicKey = source.publicKey; + } else { + return null; + } + + // Public key + return { publicKey, address: restoredAddress }; +} diff --git a/src/index.ts b/src/index.ts index 8c41fea..b08bc33 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,4 +19,12 @@ export { TonhubLocalConfig, TonhubLocalTransactionRequest, TonhubLocalTransactionResponse -} from './connector/TonhubLocalConnector'; \ No newline at end of file +} from './connector/TonhubLocalConnector'; + +export { + verifySignatureResponse +} from './connector/crypto'; + +export { + extractPublicKeyAndAddress +} from './contracts/extractPublicKeyAndAddress'; \ No newline at end of file