XYOracleNetwork/sdk-xyo-client-js

View on GitHub
packages/protocol/packages/account/src/Key/AddressValue.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { instantiateSecp256k1, Secp256k1 } from '@bitauth/libauth'
import { equalArrayBuffers, toUint8Array } from '@xylabs/arraybuffer'
import { staticImplements } from '@xylabs/static-implements'
import { Data } from '@xyo-network/data'
import { AddressValueInstance, AddressValueStatic } from '@xyo-network/key-model'
import { WasmSupport } from '@xyo-network/wasm'

import { EllipticKey } from './EllipticKey'

const wasmSupportStatic = new WasmSupport(['bigInt'])
const recoveryIds = [0, 1, 2, 3] as const

@staticImplements<AddressValueStatic>()
export class AddressValue extends EllipticKey implements AddressValueInstance {
  static readonly wasmSupport = wasmSupportStatic
  protected static _secp256k1: Promise<Secp256k1 | null>

  private _isAddress = true
  constructor(address: ArrayBuffer) {
    super(20, AddressValue.addressFromAddressOrPublicKey(address))
  }

  static addressFromAddressOrPublicKey(bytes: ArrayBuffer) {
    return bytes.byteLength === 20 ? bytes : AddressValue.addressFromPublicKey(bytes)
  }

  static addressFromPublicKey(key: ArrayBuffer): ArrayBuffer {
    return new Data(64, key).keccak256.slice(12)
  }

  static isAddress(value: unknown) {
    return (value as AddressValue)._isAddress
  }

  //there has to be a better way to do this other than trying all four numbers
  //maybe we can get the number from the address more easily
  static verify(msg: ArrayBuffer, signature: ArrayBuffer, address: ArrayBuffer) {
    let valid = false
    const sigArray = toUint8Array(signature)
    const r = sigArray.slice(0, 32)
    const s = sigArray.slice(32, 64)

    const expectedAddress = new AddressValue(address).bytes
    for (const recoveryId of recoveryIds) {
      try {
        const publicHex = AddressValue.ecContext
          .keyFromPublic(AddressValue.ecContext.recoverPubKey(toUint8Array(msg), { r, s }, recoveryId))
          .getPublic('hex')
          .slice(2)
        const publicKey = toUint8Array(publicHex)
        const recoveredAddress = AddressValue.addressFromPublicKey(publicKey)
        valid = valid || equalArrayBuffers(recoveredAddress, expectedAddress)
        if (valid) break
      } catch {
        //this catch is because the pub key it recovered is not an actual address, thus they throw
        continue
      }
    }
    return valid
  }

  static async verifyAsync(msg: ArrayBuffer, signature: Uint8Array, address: ArrayBuffer) {
    const verifier = await AddressValue.secp256k1()
    if (verifier && AddressValue.wasmSupport.canUseWasm) {
      let publicKey: Uint8Array | null = null
      for (const recoveryId of recoveryIds) {
        try {
          publicKey = verifier.recoverPublicKeyCompressed(toUint8Array(signature), recoveryId, toUint8Array(msg))
          if (verifier.validatePublicKey(publicKey)) break
        } catch (ex) {
          const error = ex as Error
          console.log(error.message)
          console.log(error.stack)
          continue
        }
      }
      if (publicKey) return verifier.verifySignatureCompact(toUint8Array(signature), publicKey, toUint8Array(msg))
    }
    // In all failure modes default to the JS implementation
    return AddressValue.verify(msg, signature, address)
  }

  protected static secp256k1(): Promise<Secp256k1 | null> {
    this._secp256k1 =
      this._secp256k1 ??
      (async () => {
        await wasmSupportStatic.initialize()
        await instantiateSecp256k1()
      })()
    return this._secp256k1
  }

  verify(msg: ArrayBuffer, signature: ArrayBuffer) {
    return AddressValue.verify(msg, signature, this.bytes)
  }
}