-- DEPRECATED --

The HW wallet should support the following functionality

  1. Deterministic key generation for key parameters
  2. Export public keys
  3. Participate in signing schemes: Schnorr's signature, Bulletproof.
    • The latter in CPU-hungry, but most of the computations can be done without HW
  4. Support multi-signatures
    • This includes both: signing with multiple self keys, as well as the keys generated by the others.
  5. Obviously secret keys must not be revealed.

Nonces and randomness

Our signing protocols follow a ritual where the signer generates some nonce(s), reveals its image(s), gets a challenge(s), and then should reveal the appropriate preimage. To guarantee (5) the signer must never answer to different challenges for the same nonce. This is absolutely vital!

In a regular signature protocol the nonce can always be generated in a deterministic way from the visible transcript and the sercret key, the challenge is derived from the visible transcript, and the whole signing process is atomic.

This approach, however, is not compatible with (4). In the case of the multisignature the signing process is not atomic. And ability to create multisignatures is essential in MW.

Hence, in order to sustain the requirements the HW should use another source of (pseudo)randomness. Moreover, the random nonce generated for the signing process should be kept inside the HW for indefinite time, because signing may take considerable time, during which HW wallet should be able to operate normally. In addition each nonce should be erased once the preimage based on it is revealed.

Proposed design

HW wallet should support the basic EC cryptography primitives for the parameters specified by the secp256k1 standard (the one that is used in bitcoin). Means - 256-bit wide keys, the same EC equation, finite field parameters, same G-generator.

The HW wallet should have non-volatile memory, represented by memory slots. There should be the following slots:

  1. Master secret.
    • No direct access to the caller.
    • Should be used as a secret for key generation.
  2. Nonce source.
    • No direct access to the caller.
    • Should be initialized by true random either during production, or upon initialization of the Master secret.
  3. Generated key.
    • Contains the generated key. May be a single key or their sum/difference (more about this later).
  4. Generated nonces.

This low-level design may look weird, but it's needed to support all the MW functionality. The following functionality should be supported:

Nonce regeneration

Parameters:

  • i - the target slot index

Result: the target slot should contain a unique nonce, derived from the Nonce source, and the Nonce source itself should be mutated immediately after that.

  • n[i] = DeriveNonce(NonceSource)
  • NonceSource = Mutate(NonceSource)

Important: all the nonce slots must be regenerated before (or immediately after) the Master secret is initialized. In simple words, there must be no situation where the HW is operational, yet there's a nonce slot which contains predictable (zero?) value.

Key slot reset (assign to zero)

Result: the target slot should contain zero value: k = 0

Key generation

Parameters:

  • ID - an opaque 256-bit data that identifies the key

Result: the generated key should be added to the target slot: k += KeyGenerate(MasterSecret, ID)

Key split

The key should be split into 2 parts in a deterministic (yet opaque) way.

Return value: k2 = Muate(k)

Result: k -= k2

Image (public key) reveal

Parameters:

  • i - the slot index.

Return value: EC point (in whatever representation) equals to the G-generator multiplied by the value of this slot: G * n[i]

Applicable for key and nonce slots

Sig1 (Schnorr)

Parameters:

  • i - the slot index
  • e - challenge

Return value: blinded preimage: kb = n[i] + e*k

Result: used nonce is immediately regenerated: Regenerate(i)

Sig2 (Bulletproof)

Parameters:

  • i1, i2 - 2 slot indexes
  • e, e2 - 2 challenges

Return value: blinded preimage: kb = n[i1] + e2*n[i2] + e*k

Result: used nonce is immediately regenerated: Regenerate(i1), Regenerate(i2)



API

Key generation

Inputs:

  • KIDV for the needed key Otputs:
  • The public key (ECC point)

Generate the key, and export its image (i.e. multiplies by G-generator)

Bulletproof generation

Inputs:

  • KIDV for the needed key Otputs:
  • The Bulletproof

Nonce generation

Inputs:

  • Slot number Otputs:
  • The nonce image (ECC point)

Generate the random nonce (ECC scalar), and export its image (i.e. multiplies by G-generator).

Transaction signature

Inputs:

  • List of KIDVs of input UTXOs
  • List of KIDVs of output UTXOs
  • Randomly generated offset (ECC scalar)
  • Number of the nonce slot
  • Challenge e (ECC scalar) // after discussion, it seems that it should be calculated on device, so we need kernel parameters
    • Fee
    • Commitment // total public excess
    • minimal height
    • maximum height
    • Amount(m_AssetEmission)
    • m_pHashLock;
    • Public nonce of 2nd party (for multisig)
    • Public excess of 2nd party (for multisig)

Outputs:

  • Signature (ECC scalar)

The HW wallet should do the following:

  1. Request user's permission for the transaction.
  2. Calculate the result.
  3. Immediately overwrite the nonce slot with another randomly-generated value.

The result is calculated by the following algorithm:

  1. Generate all the keys (i.e. blinding factors), and calculate sk as sum of inputs minus outputs.
    sk = Sum(input blinding factors) - Sum(output blinding factors) - offset
    offset is generated randomly on caller side
  2. Calculate multi signature public nonce: PublicNonce = Nonce*G + PeerPublicNonce
  3. Calculate total blinding excess: TotalExcess = sk * G + PeerPublicExcess
  4. Calculate Message for sign: hash kernel parameters see: TxKernel::get_Hash
  5. Calculate challenge by: e = H(PublicNonce|Message)
  6. Calculate the signature by: Signature = sk * e + Nonce
  7. Re-generate Nonce (overwrite the contents of the slot)
  8. Reveal Signature