ref: extract signature verify, moved documentation

main
Steve Korshakov 2022-06-17 08:15:39 +04:00
parent 37b6390602
commit bc330b0ca6
5 changed files with 79 additions and 191 deletions

134
README.md
View File

@ -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

View File

@ -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 {

41
src/connector/crypto.ts Normal file
View File

@ -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;
}

View File

@ -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 };
}

View File

@ -19,4 +19,12 @@ export {
TonhubLocalConfig,
TonhubLocalTransactionRequest,
TonhubLocalTransactionResponse
} from './connector/TonhubLocalConnector';
} from './connector/TonhubLocalConnector';
export {
verifySignatureResponse
} from './connector/crypto';
export {
extractPublicKeyAndAddress
} from './contracts/extractPublicKeyAndAddress';