All files / src/helpers keyUtils.ts

77.14% Statements 27/35
75% Branches 6/8
100% Functions 0/0
77.14% Lines 27/35

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 106 107 108 109 110 111 112 113 114 115          110x                                       110x                   17x                             17x 17x   17x 17x   17x 17x   17x 17x   17x 17x         17x         17x       6x       6x                   6x           6x         6x 6x 6x 6x 6x 6x 6x       21x    
import bs58 from 'bs58';
import { Buffer as BufferPolyfill } from 'buffer';
// @ts-expect-error: not a typescript package
import * as base64url from 'base64url-universal';
 
const buffer = typeof Buffer === 'undefined' ? BufferPolyfill : Buffer;
 
/** Secp256k1 Public Key  */
export interface IPublicKeyJwk {
  /** key type */
  kty: string;
 
  /** curve */
  crv: string;
 
  /** public point */
  x: string;
 
  /** public point */
  y?: string;
 
  /** key id */
  kid?: string;
}
 
const ECDSA_CURVE = {
  P256: 'P-256',
  P384: 'P-384',
  P521: 'P-521',
  // compatibility with @peculiar/webcrypto
  secp256k1: 'K-256',
}
 
function getSecretKeySize({ curve }: { curve: string }): number {
  if (curve === ECDSA_CURVE.P256 || curve === ECDSA_CURVE.secp256k1 || curve === 'secp256k1') {
    return 32
  }
  if (curve === ECDSA_CURVE.P384) {
    return 48
  }
  if (curve === ECDSA_CURVE.P521) {
    return 66
  }
  throw new TypeError(`Unsupported curve "${curve}".`)
}
 
function toPublicKeyBytes({ jwk } = {} as any): Uint8Array {
  if (jwk?.kty !== 'EC') {
    throw new TypeError('"jwk.kty" must be "EC".')
  }
  const { crv: curve } = jwk
  const secretKeySize = getSecretKeySize({ curve })
  // convert `x` coordinate to compressed public key
  const x = base64url.decode(jwk.x)
  const y = base64url.decode(jwk.y)
  // public key size is always secret key size + 1
  const publicKeySize = secretKeySize + 1
  const publicKey = new Uint8Array(publicKeySize)
  // use even / odd status of `y` coordinate for compressed header
  const even = y[y.length - 1] % 2 === 0
  publicKey[0] = even ? 2 : 3
  // write `x` coordinate at end of multikey buffer to zero-fill it
  publicKey.set(x, publicKey.length - x.length)
  return publicKey
}
 
/** convert jwk to hex encoded public key */
export const publicKeyHexFromJwkSecp256k1 = (jwk: IPublicKeyJwk): string => {
  return buffer.from(toPublicKeyBytes({ jwk })).toString('hex');
};
 
/** convert publicKeyHex to base58 */
export const publicKeyBase58FromPublicKeyHex = (publicKeyHex: string): string => {
  return bs58.encode(buffer.from(publicKeyHex, 'hex'));
};
 
export const publicKeyBase58FromUint8Array = (publicKey: Uint8Array<any>): string => {
  return bs58.encode(publicKey);
}
 
export function jwkToPublicKeyBytesEd25519(jwk: IPublicKeyJwk) {
  const { kty, crv, x } = jwk;
  if (kty !== 'OKP') {
    throw new TypeError('"jwk.kty" must be "OKP".');
  }
  if (crv !== 'Ed25519') {
    throw new TypeError('"jwk.crv" must be "Ed25519".');
  }
  if (typeof x !== 'string') {
    throw new TypeError('"jwk.x" must be a string.');
  }
  const publicKey = base64url.decode(jwk.x);
  if (publicKey.length !== 32) {
    throw new Error(
      `Invalid public key size (${publicKey.length}); ` +
      `expected 32.`);
  }
  return publicKey;
}
 
export const jwkToMultibaseEd25519 = (jwk: IPublicKeyJwk): string => {
  // multicodec ed25519-pub header as varint
  const MULTICODEC_PUBLIC_HEADER = new Uint8Array([0xed, 0x01]);
  const publicKeyBytes = jwkToPublicKeyBytesEd25519(jwk);
  const uint8ArrayPublicKey = new Uint8Array(MULTICODEC_PUBLIC_HEADER.length + publicKeyBytes.length);
  uint8ArrayPublicKey.set(MULTICODEC_PUBLIC_HEADER);
  uint8ArrayPublicKey.set(publicKeyBytes, MULTICODEC_PUBLIC_HEADER.length);
  const publicKeyBase58 = publicKeyBase58FromUint8Array(uint8ArrayPublicKey);
  return `z${publicKeyBase58}`;
}
 
export function publicKeyMultibaseToBytes (publicKeyMultibase: string): Uint8Array {
  return Uint8Array.from((bs58 as any).decode(publicKeyMultibase.slice(1)).slice(2))
}