<commit_before>
States =
  # Dongle juste created, not initialized.
  UNDEFINED: undefined
  # PIN required.
  LOCKED: 'locked'
  # PIN has been verified.
  UNLOCKED: 'unlocked'
  # No seed present, dongle must be setup.
  BLANK: 'blank'
  # Dongle has been unplugged.
  DISCONNECTED: 'disconnected'
  # An error appended, user must unplug/replug dongle.
  ERROR: 'error'

Firmware =
  V1_4_11: 0x0001040b0146
  V1_4_12: 0x0001040c0146
  V1_4_13: 0x0001040d0146
  V1_0_0B: 0x20010000010f

# Ledger OS pubKey, used for pairing.
Attestation =
  String: "<KEY>"
Attestation.Bytes = parseInt(hex, 16) for hex in Attestation.String.match(/\w\w/g)

# This path do not need a verified PIN to sign messages.
BitIdRootPath = "0'/0/0xb11e"

Errors = @ledger.errors

# Populate dongle namespace.
@ledger.dongle ?= {}
_.extend @ledger.dongle
  States: States
  Firmware: Firmware
  Attestation: Attestation
  BitIdRootPath: BitIdRootPath

###
Signals :
  @emit connected
  @emit state:changed(States)
  @emit state:locked
  @emit state:unlocked
  @emit state:blank
  @emit state:disconnected
  @emit state:error(args...)
###
class @ledger.dongle.Dongle extends EventEmitter

  # @property
  device_id: undefined
  # @property [String]
  state: States.UNDEFINED
  # @property [Integer]
  firmwareVersion: undefined
  # @property [Integer]
  operationMode: undefined

  # @property [BtChip]
  _btchip: undefined
  # @property [Array<ledger.wallet.ExtendedPublicKey>]
  _xpubs = []

  # @private @property [String] pin used to unlock dongle.
  _pin = undefined

  constructor: (@device_id, card) ->
    super
    @_btchip = new BTChip(card)
    @_recoverFirmwareVersion().then( =>
      return @_recoverOperationMode().q()
    ).then( =>
      # Set dongle state on failure.
      return @getPublicAddress("0'/0/0").q()
    ).then( =>
      # @todo Se connecter directement à la carte sans redemander le PIN
      console.warn("Dongle is already unlock ! Case not handle => Pin Code Required.")
      @_setState(States.LOCKED)
    ).done()

  # Called when 
  disconnect: () -> @_setState(States.DISCONNECTED)

  # @return [String] Firmware version, 1.0.0 for example.
  getStringFirmwareVersion: -> @firmwareVersion.byteAt(1) + "." + @firmwareVersion.byteAt(2) + "." + @firmwareVersion.byteAt(3)
  
  # @return [Integer] Firmware version, 0x20010000010f for example.
  getIntFirmwareVersion: -> @firmwareVersion

  # @return [ledger.fup.FirmwareUpdater]
  getFirmwareUpdater: () -> ledger.fup.FirmwareUpdater.instance

  # @return [Boolean]
  isFirmwareUpdateAvailable: () -> @getFirmwareUpdater().isFirmwareUpdateAvailable(this)

  # Verify that dongle firmware is "official".
  # @param [Function] callback Optional argument
  # @return [CompletionClosure]
  isCertified: (callback=undefined) ->
    completion = new CompletionClosure(callback)
    randomValues = new Uint32Array(2)
    crypto.getRandomValues(randomValues)
    random = _.str.lpad(randomValues[0].toString(16), 8, '0') + _.str.lpad(randomValues[1].toString(16), 8, '0')
    # 24.2. GET DEVICE ATTESTATION
    @_sendApdu(new ByteString("E0"+"C2"+"00"+"00"+"08"+random, HEX), [0x9000])
    .then (result) =>
      result = result.toString(HEX)
      fields = result.match(/^(\w{8})(\w{8})(\w{2})(\w{2})(\w{4})(\w{2})(\w{2})(\w{2})(\w{2})(\w+)$/)[1..-1]
      fieldsSigned = attestation[2..8]
      signature = attestation[-1]
      @attestation =
        raw: result
        keyBatchId: attestation.substring(0, 8)
        keyDerivationId: attestation.substring(8, 16)
        supportedOperationBitFlag: attestation.substring(16, 18)
        currentOperationMode: attestation.substring(18, 20)
        firmwareMajor: attestation.substring(20, 24)
        firmwareMinor: attestation.substring(24, 26)
        firmarePatch: attestation.substring(26, 28)
        loaderIdMajor: attestation.substring(28, 30)
        loaderIdMinor: attestation.substring(30, 32)
        signature: attestation.substring(32)
      l "Device attestation :", fieldsSigned, random, signature
      try
        SHA256 = new JSUCrypt.hash.SHA256()
        domain = JSUCrypt.ECFp.getEcDomainByName("secp256k1")
        aKey = ledger.wallet.Attestation.String.match(/^(\w{2})(\w{64})(\w{64})$/)
        affinePoint = new JSUCrypt.ECFp.AffinePoint(aKey[2], aKey[3], ecdhdomain.curve)
        pubKey = new JSUCrypt.key.EcFpPublicKey(256, domain, affinePoint)
        ecsig = new JSUCrypt.signature.ECDSA(SHA256)
        ecsig.init(pubKey, JSUCrypt.signature.MODE_VERIFY)
        sigBytes = (parseInt(n, 16) for n in @attestation.signature.match(/\w\w/g))
        verif = ecsig.verify(random, sigBytes)
        l sigBytes
        l 'Verif', verif
      catch err
        error = new Errors.StandardError(Errors.SignatureError, err)
        completion.failure(error)
    .fail (err) =>
      error = new Errors.StandardError(Errors.SignatureError, err)
      completion.failure(error)
    .done()
    completion.readonly()

  # Return asynchronosly state. Wait until a state is set.
  # @param [Function] callback Optional argument
  # @return [CompletionClosure]
  getState: (callback=undefined) ->
    completion = new CompletionClosure(callback)
    if @state is States.UNDEFINED
      @once 'state:changed', (e, state) => completion.success(state)
    else
      completion.success(@state)
    completion.readonly()

  # @param [String] pin ASCII encoded
  # @param [Function] callback Optional argument
  # @return [CompletionClosure]
  unlockWithPinCode: (pin, callback=undefined) ->
    throw new ledger.StandardError(Errors.DongleAlreadyUnlock) if @state isnt States.LOCKED
    completion = new CompletionClosure(callback)<commit_msg>Fix isCertified and add getRawFirmwareVersion to Dongle<commit_after>
States =
  # Dongle juste created, not initialized.
  UNDEFINED: undefined
  # PIN required.
  LOCKED: 'locked'
  # PIN has been verified.
  UNLOCKED: 'unlocked'
  # No seed present, dongle must be setup.
  BLANK: 'blank'
  # Dongle has been unplugged.
  DISCONNECTED: 'disconnected'
  # An error appended, user must unplug/replug dongle.
  ERROR: 'error'

Firmware =
  V1_4_11: 0x0001040b0146
  V1_4_12: 0x0001040c0146
  V1_4_13: 0x0001040d0146
  V1_0_0B: 0x20010000010f

# Ledger OS pubKey, used for pairing.
Attestation =
  String: "<KEY>"
Attestation.Bytes = parseInt(hex, 16) for hex in Attestation.String.match(/\w\w/g)

# This path do not need a verified PIN to sign messages.
BitIdRootPath = "0'/0/0xb11e"

Errors = @ledger.errors

# Populate dongle namespace.
@ledger.dongle ?= {}
_.extend @ledger.dongle
  States: States
  Firmware: Firmware
  Attestation: Attestation
  BitIdRootPath: BitIdRootPath

###
Signals :
  @emit connected
  @emit state:changed(States)
  @emit state:locked
  @emit state:unlocked
  @emit state:blank
  @emit state:disconnected
  @emit state:error(args...)
###
class @ledger.dongle.Dongle extends EventEmitter

  # @property
  device_id: undefined
  # @property [String]
  state: States.UNDEFINED
  # @property [Integer]
  firmwareVersion: undefined
  # @property [Integer]
  operationMode: undefined

  # @property [BtChip]
  _btchip: undefined
  # @property [Array<ledger.wallet.ExtendedPublicKey>]
  _xpubs = []

  # @private @property [String] pin used to unlock dongle.
  _pin = undefined

  constructor: (@device_id, card) ->
    super
    @_btchip = new BTChip(card)
    @_recoverFirmwareVersion().then( =>
      return @_recoverOperationMode().q()
    ).then( =>
      # Set dongle state on failure.
      return @getPublicAddress("0'/0/0").q()
    ).then( =>
      # @todo Se connecter directement à la carte sans redemander le PIN
      console.warn("Dongle is already unlock ! Case not handle => Pin Code Required.")
      @_setState(States.LOCKED)
    ).done()

  # Called when 
  disconnect: () -> @_setState(States.DISCONNECTED)

  # @return [String] Firmware version, 1.0.0 for example.
  getStringFirmwareVersion: -> @firmwareVersion.byteAt(1) + "." + @firmwareVersion.byteAt(2) + "." + @firmwareVersion.byteAt(3)
  
  # @return [Integer] Firmware version, 0x20010000010f for example.
  getIntFirmwareVersion: -> @firmwareVersion

  ###
    Gets the raw version {ByteString} of the dongle.

    @param [Boolean] isInBootLoaderMode Must be true if the current dongle is in bootloader mode.
    @param [Boolean] forceBl Force the call in BootLoader mode
    @param [Function] callback Called once the version is retrieved. The callback must be prototyped like size `(version, error) ->`
    @return [CompletionClosure]
  ###
  getRawFirmwareVersion: (isInBootLoaderMode, forceBl=no, callback=undefined) ->
    completion = new CompletionClosure(callback)
    apdu = new ByteString((if !isInBootLoaderMode and !forceBl then "E0C4000000" else "F001000000"), HEX)
    @_sendApdu(apdu).then (result) =>
      sw = @_btchip.card.SW
      if !isInBootLoaderMode and !forceBl
        if sw is 0x9000
          completion.success([result.byteAt(1), (result.byteAt(2) << 16) + (result.byteAt(3) << 8) + result.byteAt(4)])
        else
          # Not initialized now - Retry
          @getRawFirmwareVersion(isInBootLoaderMode, yes).thenForward(completion)
      else
        if sw is 0x9000
          completion.success([0, (result.byteAt(5) << 16) + (result.byteAt(6) << 8) + result.byteAt(7)])
        else if !isInBootLoaderMode and (sw is 0x6d00 or sw is 0x6e00)
          #  Unexpected - let's say it's 1.4.3
          completion.success([0, (1 << 16) + (4 << 8) + (3)])
        else
          completion.failure(new ledger.StandardError(ledger.errors.UnknowError, "Failed to get version"))
    .fail (error) ->
      completion.failure(new ledger.StandardError(ledger.errors.UnknowError, error))
    completion.readonly()

  # @return [ledger.fup.FirmwareUpdater]
  getFirmwareUpdater: () -> ledger.fup.FirmwareUpdater.instance

  # @return [Boolean]
  isFirmwareUpdateAvailable: () -> @getFirmwareUpdater().isFirmwareUpdateAvailable(this)

  # Verify that dongle firmware is "official".
  # @param [Function] callback Optional argument
  # @return [CompletionClosure]
  isCertified: (callback=undefined) ->
    completion = new CompletionClosure(callback)
    randomValues = new Uint32Array(2)
    crypto.getRandomValues(randomValues)
    random = _.str.lpad(randomValues[0].toString(16), 8, '0') + _.str.lpad(randomValues[1].toString(16), 8, '0')
    # 24.2. GET DEVICE ATTESTATION
    @_sendApdu(new ByteString("E0"+"C2"+"00"+"00"+"08"+random, HEX), [0x9000])
    .then (result) =>
      attestation = result.toString(HEX)
      dataToSign = attestation.substring(16,32) + random
      dataSig = attestation.substring(32)
      dataSigBytes = (parseInt(n,16) for n in dataSig.match(/\w\w/g))

      sha = new JSUCrypt.hash.SHA256()
      domain = JSUCrypt.ECFp.getEcDomainByName("secp256k1")
      affinePoint = new JSUCrypt.ECFp.AffinePoint(Attestation.xPoint, Attestation.yPoint)
      pubkey = new JSUCrypt.key.EcFpPublicKey(256, domain, affinePoint)
      ecsig = new JSUCrypt.signature.ECDSA(sha)
      ecsig.init(pubkey, JSUCrypt.signature.MODE_VERIFY)
      if ecsig.verify(dataToSign, dataSigBytes)
        completion.success()
      else
        completion.failure()
    .fail (err) =>
      error = new Errors.StandardError(Errors.SignatureError, err)
      completion.failure(error)
    .done()
    completion.readonly()

  # Return asynchronosly state. Wait until a state is set.
  # @param [Function] callback Optional argument
  # @return [CompletionClosure]
  getState: (callback=undefined) ->
    completion = new CompletionClosure(callback)
    if @state is States.UNDEFINED
      @once 'state:changed', (e, state) => completion.success(state)
    else
      completion.success(@state)
    completion.readonly()

  # @param [String] pin ASCII encoded
  # @param [Function] callback Optional argument
  # @return [CompletionClosure]
  unlockWithPinCode: (pin, callback=undefined) ->
    throw new ledger.StandardError(Errors.DongleAlreadyUnlock) if @state isnt States.LOCKED
    completion = new CompletionClosure(callback)
{'fraction_non_alphanumeric': 0.12185992347363167, 'fraction_numerical': 0.03252370653801364, 'mean_word_length': 3.6873294346978556, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5421240', 'n_tokens_mistral': 4353, 'n_tokens_neox': 3986, 'n_words': 930}