# Hinchy's High Level Phonon Protocol Spec ## Todo - Update `sendPhonon()`, `receivePhonon()`, `sendBalance()` & `receiveBalance()` to include how locks are transferred. - Add `lockBalance()` & `unlockBalance()` methods - Understand https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#pinProto1 and update commands and responses accordingly ## Notes - I believe the applet needs to track two integer counters. The first, `activeTransactionCounter`, is used when the receiving party is active (think NFC). Second, `passiveTransactionCount`, is for when the receiver isn't active and a third party is tracking and issuing counters (phonon inbox service). By maintaining two counters, a user can setup an external service to receive packets for them while still using their applet without counter conflict. - Can the applet cache messages that if lost would result in loss of funds? Should that be the job of the client? - Martin has cautioned against using indexes as IDs as this can lead to a range of bugs. I'm not knowledgeable on Java Card's limitations so I've avoided specifying how a phonon should be identified and retrieved. - Need to discuss card life. How many writes? - Update the transfer and consume documentation to account for a transfer packet being able to include multiple phonons. ## High Level Transaction Overviews For simplicity, the entities Alice and Shop Keeper represent both the human and the client they using to interact with their applets. ### Active Recipient Transfer ![](https://i.imgur.com/a1u9X0Y.png) 5. Additional details can be included such as an order Id amount what assets are requested. This information is not part of the Phonon Protocol. 7. The phonon is modified so that any future "create transfer packet" requests for it that aren't for the shop keeper will fail. 10. The transfer packet includes a signature that allows the shop keeper to verify its contents and validity before consuming it. #### Transport **NFC** Alice needs to tap her device at step 5 and 9. **QR Code** Alice needs to scan a QR code at step 5 and the shop keeper needs to scan a QR at step 9. One way to require only one user action would be for clients to include additional data for where the transfer packet is sent. For example, at step 9, Alice could send the transfer packet to web server that forwards it to the shop keeper. ### Passive Recipient Transfer ![](https://i.imgur.com/0971CeG.png) ## Phonon (Key Pair) - Consists of a key pair and associated metadata - At any point in time only a single applet can transfer a phonon or reveal it's private key - Main use case is encumbering cryptocurrency ### createPhonon() #### Command Payload ``` { curveType: secp256k1 | TBD metadata: JSON } ``` #### Business Logic - error if `applet.phonons.length` == `MAX_PHONON_COUNT` - error if `payload.curveType` is not supported - generate `keyPair` from `payload.curveType` - derive `phononId` from `keyPair.publicKey` (if required) - create a "phonon" containing `phononId`, `payload.metadata` & `keyPair.privateKey` and append to `applet.phonons` #### Command Success Response ``` { phononId: TBD publicKey: string } ``` ### redeemPhonon() #### Command Payload ``` { phononId: TBD } ``` #### Business Logic - `phonon = applet.phonons[payload.phononId]` - error if `phonon` doesn't exist - error if `phonon.lock` exists - remove `phonon` from `applet.phonons` #### Command Success Response ``` { privateKey: string } ``` ### sendPhonon() #### Command Payload ``` { phononId: TBD recipientsPublicKey: string nonceType: 'active' | 'passive' nonce: number } ``` #### Business Logic - `phonon` = `applet.phonons[payload.phononId]` - error if `phonon` doesn't exist - error if `phonon.lock` exists & `phonon.lock.recipient` != `payload.recipientsPublicKey` - `encryptedPhononPrivateKey = encrypt(recipientsPublicKey, phonon.privateKey)` - generate `signature` from `sign(applet.identityKey, hash(payload.recipientsPublicKey, payload.nonceType, payload.nonce, phonon.publicKey, encryptedPhononPrivateKey ))` - delete `phonon` from `applet.phonons` #### Command Success Response ``` { signature: string } ``` ### receivePhonon() #### Command Payload ``` { phononPublicKey: string encryptedPhononPrivateKey: string nonceType: 'active' | 'passive' nonce: number value: number sendersCertificate: string metadata: string signature: string } ``` #### Business Logic - error if `payload.sendersCertificate.trustRoot` != `applet.certificate.trustRoot` - error if `applet[payload.nonceType]` >= `payload.nonce` - error if `validateSignture(payload.sendersCertificate.publicKey, payload.signature, hash(applet.identityPublicKey, payload.nonceType, payload.nonce, phonon.publicKey, encryptedPhononPrivateKey))` fails - decrypt `payload.encryptedPhononPrivateKey` - derive `phononId` from `keyPair.publicKey` (if required) - create a "phonon" containing `phononId`, `payload.metadata` & `keyPair.privateKey` and append to `applet.phonons` #### Command Success Response ``` { phononId: TBD publicKey: string } ``` ## Balances (Previously "Flexible Phonons") - Allows any entity able to sign the ability to issue assets. Can be thought of as "the ERC20 of the Phonon Protocol". - The applet maintains a list of assets and their balances Hinchy is setting up music festival. He wants to issue his own "token" on phonon applets for festival goers to use at food trucks. Alice is attending the festival and wants to buy $100 worth of "Hinch Bucks" 1. Hinchy generates a key pair on his secure laptop. 2. Alice transfers $100 to Hinchy and gives him her phonon applet identity key and nonce. 3. Hinchy generates a signature with his private key containing Alice's public key, "Hinch Bucks", nonce and 100. 4. Alice consumes this signature by calling `increaseBalance`. Her applet derives an assetId from Hinchy's public key and "Hinch Bucks" and increases her balance by 100. 5. Alice buys a burger. Her balance is reduced by 20 when she calls `sendBalance` and the Burger store's balance increases by 20 when they call `receiveBalance` 7. At the end of the festival Alice has 80 Hinch Bucks left. She calls `reduceBalance` which generates a signature proving she burnt 80 hinch bucks. 8. She sends this signature to Hinchy who transfers $80 to her bank account. The above user story is used to illustrate the flow but much more powerful use cases exist. For example, a government issuing a CBDC or a decentralised network issuing claims on cryptocurrencies deposited in a smart contract. ### increaseBalance() #### Command Payload ``` { issuerPublicKey: string, assetName: string nonceType: 'active' | 'passive' nonce: number value: number signature: string } ``` #### Business Logic - `balance = applet.balances[payload.assetId]` - error if `applet[payload.nonceType]` >= `payload.nonce` - error if `validateSignture(payload.issuerPublicKey, payload.signature, hash(applet.identityPublicKey, payload.assetName, payload.nonceType, payload.nonce, payload.value))` fails - derive the `assetId` from `payload.issuerPublicKey` & `payload.assetName` - increase `balance` by `payload.value` #### Command Success Response ``` { balance: number } ``` ### sendBalance() #### Command Payload ``` { assetId: string recipientsPublicKey: string nonceType: 'active' | 'passive' nonce: number value: number } ``` #### Business Logic - `balance = applet.balances[payload.assetId]` - error if `payload.value` > `balance`. Ensure "locked" balances are not included. - generate `signature` from `sign(applet.identityKey, hash(applet.identityPublicKey, payload.assetId, payload.nonceType, payload.nonce, payload.value))` - reduce balance for `balance` by `payload.value` #### Command Success Response ``` { signature: string } ``` ### receiveBalance() #### Command Payload ``` { assetId: string nonceType: 'active' | 'passive' nonce: number value: number sendersCertificate: string signature: string } ``` #### Business Logic - error if `payload.sendersCertificate.trustRoot` != `applet.certificate.trustRoot` - error if `applet[payload.nonceType]` >= `payload.nonce` - error if `validateSignture(payload.sendersCertificate.publicKey, payload.signature, hash(payload.assetId, payload.nonceType, payload.nonce, payload.value))` fails - increase balance for `applet.balances[payload.assetId]` by `payload.value` #### Command Success Response ``` { balance: number } ``` ### reduceBalance() #### Command Payload ``` { assetId: string value: number } ``` #### Business Logic - `balance = applet.balances[payload.assetId]` - error if `payload.value` > `balance`. Ensure "locked" balances are not included. - generate `signature` from `sign(applet.identityKey, hash(payload.assetId, payload.value))` - reduce balance for `balance` by `payload.value` #### Command Success Response ``` { signature: string } ``` ## Locks - Previously referred to as "Locked Phonons" - A mechanism for preventing a phonon from being redeemed and restricting who it can be sent - Can be leveraged to reduce counterparty risk by making it economically expensive for a party to not uphold their part of a trade. - Other use cases such as locking value until a signed message from an "oracle". - Parties - Alice's Applet - Bob's Applet - Mediator - An entity that, in some cases, can provide a signature to "unlock" a Phonon or Balance. Alice & Bob each have phonons the phonon wants. They do not trust each other and wish to exchange phonons. They have provided the other party the public key of the phonon they currently have and each have validated it contains the asset they want. **Happy Path** The mediator does not get involved and has no knowledge of the trade. 1. Alice & Bob agree to use Phonon DAO mediator service. 2. Alice generates a unique nonce for their trade. This can be anything but an honest actor will want a unique nonce. The nonce can include parameters about the trade such as how long each user has to complete their part of the trade. In this case Alice generates a nonce that is hash of each of their card’s public keys, the public keys of the phonons they are exchanging, the current time and agreement that each party has 5 minutes to complete each step. 3. Alice sends the nonce to Bob and he approves it. 4. Alice locks her phonon by invoking `lockPhonon()` and passing in the agreed nonce, the mediator’s public key and the Bob's applet’s public key. Her applet returns a "proof of lock" signed message. 5. Alice sends her proof of lock to Bob who validated it. 6. Bob locks his phonon by invoking `lockPhonon()` and passing in the agreed nonce, the mediator’s public key and the Alice's applet’s public key. His applet returns a "proof of lock" signed message. 7. Bob sends his proof of lock to Alice who validates it. 8. Alice generates a phonon transfer packet by invoking `sendPhonon()`. She sends this to Bob. 9. Bob consumes the transfer packet from Alice with `receivePhonon`. Bob now has the phonon he wants but it is locked. 10. Bob generates a phonon transfer packet by invoking `sendPhonon()`. He sends this to Alice. 11. Alice consumes the transfer packet from Bob with `receivePhonon`. Alice now has the phonon she wants but it is locked. 12. Alice generates an unlock signature by invoking `generateUnlockPhononSignature()`. She sends the signature to Bob. 13. Bob unlocks his phonons by invoking `unlockPhonon` passing in the unlock signature from Alice. 14. Bob generates an unlock signature by invoking `generateUnlockPhononSignature()`. She sends the signature to Alice. 15. Alice unlocks her phonons by invoking `unlockPhonon` passing in the unlock signature from Bob. **Unhappy Path 1** - Mediator is aware of attempted trade - No economic harm to either party 1. Same as happy path 2. Same as happy path 3. Same as happy path 4. Same as happy path 5. Same as happy path 6. Bob doesn't lock his phonon 7. Alice informs the mediator that Bob she hasn't received a proof of lock from Bob. 8. Bob doesnt respond in the agreed upon time 9. The mediator issues a signature unlocking Alice's phonon. 10. Alice unlocks her phonon. **Unhappy Path 2** - Mediator is aware of attempted trade - Possible economic harm to Bob depending on the mediator's policy 1. Same as happy path 2. Same as happy path 3. Same as happy path 4. Same as happy path 5. Same as happy path 6. Same as happy path 7. Bob fails to send lock of proof to Alice 8. Alice informs the mediator that Bob she hasn't received a proof of lock from Bob. 9. Bob doesnt respond in the agreed upon time 10. The mediator issues a signature unlocking Alice's phonon. 11. Alice unlocks her phonon. 12. Bob may contact the mediator requesting his phonon is unlocked. This may or may not be approved. **Unhappy Path 3** - Mediator is aware of attempted trade - Loss of funds for both parties 1. Same as happy path 2. Same as happy path 3. Same as happy path 4. Same as happy path 5. Same as happy path 6. Same as happy path 7. Same as happy path 8. Same as happy path 9. Same as happy path 10. Bob fails to send Alice a transfer packet 11. Alice sends the transfer packet to the mediator 12. The mediator waits for Bob to send the transfer packet 13. Bob fails to send the transfer packet. 14. The mediator refuses any future requests from Bob for his phonon to be unlocked. **Unhappy Path 4** - Mediator is aware of attempted trade - No economic harm to either party 1. Same as happy path 2. Same as happy path 3. Same as happy path 4. Same as happy path 5. Same as happy path 6. Same as happy path 7. Same as happy path 8. Same as happy path 9. Same as happy path 10. Same as happy path 11. Same as happy path 12. Same as happy path 13. Same as happy path 14. Bob fails to send Alice a phonon unlock signature 15. Alice sends the transfer packet to the mediator 16. The mediator waits for Bob's response 17. The mediator issues a signature unlocking Alice's phonon. 18. Alice unlocks her phonon. ### lockPhonon() #### Command Payload ``` { phononId: TBD, nonce: string mediatorPublicKey: string counterpartyPublicKey: string | null } ``` #### Business Logic - `phonon` = `applet.phonons[payload.phononId]` - error if `phonon` doesn't exist - error if `phonon.lock` exists - set `phonon.lock.nonce = payload.nonce` - set `phonon.lock.mediatorPublicKey = payload.mediatorPublicKey` - set `phonon.lock.counterpartyPublicKey = payload.counterpartyPublicKey` - generate `signature` from `sign(applet.identityKey, hash(phonon.publicKey, phonon.lock.nonce, phonon.lock.mediatorPublicKey, phonon.lock.counterpartyPublicKey))` #### Command Success Response ``` { signature: string } ``` ### generateUnlockPhononSignature() #### Command Payload ``` { phononPublicKey: string, nonce: string counterpartyPublicKey: string } ``` #### Business Logic - generate `signature` from `sign(applet.identityKey, hash(payload.phononPublicKey, payload.nonce, payload.counterpartyPublicKey))` #### Command Success Response ``` { signature: string } ``` ### unlockPhonon() #### Command Payload ``` { phononId: TBD, from: 'counterparty' | 'mediator' signature: string } ``` #### Business Logic - `phonon` = `applet.phonons[payload.phononId]` - error if `phonon` doesn't exist - error if `phonon.lock` doesn't exist - error if `validateSignture(phonon.lock[payload.from], payload.signature, hash(phonon.publicKey, phonon.lock.nonce, applet.identityKey))` fails - remove lock from `phonon` #### Command Success Response ``` { success: true } ``` Receive As Alice, I want to be able to receive key pairs and their associated metadata from Bob. I need to be confident that when I receive a key pair I am the only entity that has the ability to derive the associated cryptocurrency private key and access the encumbers assets. I want to be able to receive key pairs from Bob through all existing internet based communication protocols, such as email, as well as be able to receive them without an internet connection when meeting Bob in person. Trade As Alice, I want the ability to exchange key pairs and their associated meta with Bob without minimal counter party risk.