All files / src/connect/message-signing structured-message.ts

85.71% Statements 30/35
58.33% Branches 14/24
83.33% Functions 5/6
81.48% Lines 22/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105                4x 4x   4x   4x 4x 4x   4x   15x   9x             4x       6x     4x         4x   4x           4x           4x       1x   1x           1x   1x                 1x     4x                                                
import {
  ClarityValue,
  cvToHex,
  hexToCV,
  serializeCV,
  stringAsciiCV,
  tupleCV,
  uintCV,
} from 'micro-stacks/clarity';
import { ChainID, cleanHex, concatByteArrays } from 'micro-stacks/common';
import { Json } from 'micro-stacks/crypto';
import { openSignStructuredDataPopup } from '../popup';
import type { SignedOptionsWithOnHandlers, StructuredSignatureRequestOptions } from './types';
import { sha256 } from '@noble/hashes/sha256';
import { safeGetPublicKey } from '../common/utils';
import { createWalletJWT } from '../common/create-wallet-jwt';
 
const structuredDataPrefix = Uint8Array.from([0x53, 0x49, 0x50, 0x30, 0x31, 0x38]); // SIP018
 
export const makeClarityHash = (clarityValue: ClarityValue) => sha256(serializeCV(clarityValue));
 
export const makeDomainTuple = (name: string, version: string, chainId: ChainID) =>
  tupleCV({
    name: stringAsciiCV(name),
    version: stringAsciiCV(version),
    'chain-id': uintCV(chainId),
  });
 
export const makeStructuredDataHash = (
  domainHash: Uint8Array,
  structuredMessageHash: Uint8Array
) => {
  return sha256(concatByteArrays([structuredDataPrefix, domainHash, structuredMessageHash]));
};
 
export const getStructuredDataHashes = (options: {
  message: StructuredSignatureRequestOptions['message'];
  domain: StructuredSignatureRequestOptions['domain'];
}) => {
  const message: ClarityValue =
    typeof options.message === 'string' ? hexToCV(options.message) : options.message;
 
  const domain = makeDomainTuple(
    options.domain.name,
    options.domain.version,
    options.domain.chainId ?? ChainID.Mainnet
  );
 
  return {
    message: makeClarityHash(message),
    domain: makeClarityHash(domain),
  };
};
 
export const generateSignStructuredDataPayload = async (
  options: StructuredSignatureRequestOptions
) => {
  const message: string =
    typeof options.message !== 'string' ? cvToHex(options.message) : options.message;
 
  const domainTuple = makeDomainTuple(
    options.domain.name,
    options.domain.version,
    options.domain.chainId ?? options.network?.chainId ?? ChainID.Mainnet
  );
 
  const domain: string = cvToHex(domainTuple);
 
  const payload: Json = {
    stxAddress: options.stxAddress,
    message: cleanHex(message),
    domain: cleanHex(domain),
    appDetails: options.appDetails,
    publicKey: safeGetPublicKey(options.privateKey),
    network: options.network as any,
  };
 
  return createWalletJWT(payload, options?.privateKey);
};
 
export const handleSignStructuredDataRequest = async (
  options: SignedOptionsWithOnHandlers<StructuredSignatureRequestOptions>
) => {
  try {
    const token = await generateSignStructuredDataPayload({
      message: options.message,
      domain: options.domain,
      privateKey: options.privateKey,
      stxAddress: options.stxAddress,
      authOrigin: options.authOrigin,
      appDetails: options.appDetails,
      network: options.network,
    });
 
    return openSignStructuredDataPopup({
      token,
      onFinish: options.onFinish,
      onCancel: options.onCancel,
    });
  } catch (e: unknown) {
    console.error(`[micro-stacks] handleSignStructuredDataRequest failed`);
    console.error(e);
  }
};