Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
454 lines (372 sloc) 17.2 KB

Ledger Practicals (0.8.40)

The Brave Ledger is a BTC-based micropayments system for users and publishers.

This document describes the details of how the Ledger Principles are implemented. In this version of the document, only ad-free bravery is discussed.

A Brief Summary of Anonize2 operations

There are two parts to the authorized-but-anonymous protocol:

  • interacting with a registrar for the purpose of creating a credential; and,

  • interacting with a surveyor for the purpose of receiving a proof that allows a submission that is known to correspond to an authorized credential, but does not identify which credential created the submission. However, duplicate submissions are detected.

For the first part, there are two steps:

  • GET /v1/registrar/{registrarType} - returns the public key associated with a registrar; and,

  • POST /v1/registrar/{registrarType}/{uId} - contains a proof generated by the client, and returns a corresponding verification from the registrar.

For the ledger there are two types of registrars:

  • persona - used to generate a client wallet, funded by the user's personal wallet(s), that makes contributions to the ledger for visits to the publisher's site; and,

  • viewing - used to authorized multiple publisher ballots to statically report visits to an individual publisher's site.

For the second part, there are also two steps:

  • GET /v1/surveyor/{surveyorType}/{surveyorId}/{uID} - returns parameters associated with a surveyor allowing the client to construct a proof that demonstrates authorization but is anonymous; and,

  • PUT /v1/surveyor/{surveyortype}/{surveyorId} - contains a proof with a payload.

For the ledger there are two types of surveyors:

  • contribution - used to retrieve information about the current reconciliation period; and,

  • voting - used to submit a ballot for a publisher.

Each contribution surveyor has one or more voting surveyors associated with it. These allow the client to cast independent votes that are anonymous.

Ad-free personas

Client State

The client has various properties stored in persistent, secure storage. In the context of this document:

  • personaId - used to create a client wallet;

  • wallet.paymentId - an opaque identifier for the wallet;

  • wallet.address - the Bitcoin address of the wallet;

  • reconcileStamp - a timestamp indicating the end of the current period completes;

  • transactions - an array indicating successful contributions; and,

  • ballots - an array indicating publisher votes waiting to be processed as authorized-but-anonymous submissions.

How Wallets are created

When the user directs the client to create a wallet, the client generates a "user" keychain, e.g.,

{ xpub : 'xpub...'
, xprv : 'xprv...'
}

It generates an unpredictable passphrase, and then generates encryptedXprv by encrypting the xprv field using the passphrase.

The client then generates a version 4 UUID to use as a personaId, and creates a credential making the GET /v1/registrar/persona request:

<<< GET /v1/registrar/persona
<<< content-type: application/json; charset=utf-8
<<< accept-encoding:
<<<
>>> HTTP/1.1 200
>>> cache-control: no-cache
>>> content-type: application/json; charset=utf-8
>>> date: Wed, 11 Jul 2016 03:51:35 GMT
>>> vary: accept-encoding
>>>
>>> { "registrarVK" : "..." }

Following this is the POST /v1/registrar/persona/{personaId} request:

<<< POST /v1/registrar/persona/25e7eb34fe3f6b89ad9575dbf0fb3fa
<<< content-type: application/json; charset=utf-8
<<< accept-encoding:
<<<
<<< { "keychains" :
<<<   { "user"    : { "xpub" : "xpub...", "encryptedXprv" : "..." } }
<<<   , "proof"   : "..." 
<<< }
>>> HTTP/1.1 200
>>> cache-control: no-cache
>>> content-type: application/json; charset=utf-8
>>> date: Wed, 11 Jul 2016 03:51:35 GMT
>>> vary: accept-encoding
>>>
>>> { "wallet"       :
>>>   { "paymentId"  : "e43bc29a-7048-486c-b75b-6c4970b4ce2a"
>>>   , "address"    : "38ScidXjzHD1yaJ9QHSFN6JUchspCHSfnA"
>>>   }
>>> , "payload"      : { "adFree" : { "fee" : { "USD" : 5 }, "days" : 30 } }
>>> , "verification" : "..."
>>> }

During the processing of the request, the ledger creates a hierarchical deterministic (HD) wallet (with m=2 and n=3) using the user.xpub value along with a "provably unspendable" xpub value, in conjunction with the Bitcoin Wallet Provisioning server. Note that although the ledger created the client wallet, it does not hold any of the private keys necessary to unlock the wallet.

After the client verifies the registrar's signature, the client stores the paymentId and address properties in persistent, secure storage, and generates a "recovery file" containing the paymentId, the passphrase used to generate encryptedXprv, along with instructions on how to recover funds from the client wallet. The user is then strongly urged to print this file or upload it to an independent, secure service.

How Reconcilation Periods are Provisioned

There are two kinds of surveyors: "contribution surveyors" and "voting surveyors".

When a contribution surveyor is provisioned, it is created along with one or more associated "voting surveyors", and these properties:

{ "currency" : "USD"
, "amount"   : 5
, "satoshis" : 845480
, "votes"    : 5
}

When a client makes a contribution, the associated PUT /v1/wallet/{paymentId} request includes both the surveyorId of the contribution surveyor, and a client-generated viewingId (another version 4 UUID) that -- after the contribution is processed by the ledger -- is used to create a credential authorized for one or more of the voting surveyors associated with the contribution surveyor. When the credential is created, the result from the ledger includes an array of surveyorIds corresponding to the authorized voting surveyors for that credential.

How Contributions are Submitted

When the reconcileStamp property goes into the past, the client may notify the user that it is time to make a contribution.

The client makes the GET /v1/surveyor/contribution/current/{personaId} request to get information about the contribution surveyor to be used for this contribution:

<<< GET /v1/surveyor/contribution/current/25e7eb34fe3f6b89ad9575dbf0fb3fa
<<< content-type: application/json; charset=utf-8
<<< accept-encoding:
<<<
>>> HTTP/1.1 200
>>> cache-control: no-cache
>>> content-type: application/json; charset=utf-8
>>> date: Wed, 10 Aug 2016 03:51:34 GMT
>>> vary: accept-encoding
>>>
>>> { "signature"   : "..."
>>> , "payload"     : { "adFree" : { "fee"      : { "USD" : 5 }
>>>                                , "satoshis" : 845480
>>>                                , "votes"    : 5
>>>                                }
>>>                   }
>>> , "surveyorId"  : "9czn9x3e26WGmmC09FapUmret1fjupI1lGC6Q7g0H9b"
>>> , "surveyVK"    : "..."
>>> , "registrarVK" : "..."
>>> }

The contribution amount is found in the contribution surveyor's payload -- this information is advisory.

On success, the client makes the GET /v1/wallet/{paymentId} request to see if the its wallet has sufficient funds, and, if so, to receive an unsigned transaction to effect funds transfer from the wallet to a settlement account:

<<< GET /v1/wallet/e43bc29a-7048-486c-b75b-6c4970b4ce2a?refresh=true&amount=5&currency=USD
<<< content-type: application/json; charset=utf-8
<<< accept-encoding:
<<<
>>> HTTP/1.1 200
>>> cache-control: no-cache
>>> content-type: application/json; charset=utf-8
>>> date: Wed, 10 Aug 2016 03:51:35 GMT
>>> vary: accept-encoding
>>>
>>> { "paymentStamp" : 0
>>> , "rates"        : { "USD" : 591.43 }
>>> , "balance"      : "0.0335"
>>> , "satoshis"     : 3350000
>>> , "buyURL"       : "https://buy.coinbase.com?..."
>>> , "unsignedTx"   : { ... }
>>> }

The client periodically checks to see when its wallet is sufficiently funded. (After the transfer of funds is authorized from a personal wallet to the client's wallet, it may take up to 30 minutes before the Bitcoin is available in the client's wallet.) Instead of using the GET /v1/wallet/{paymentId} request, the client may primarily use a pool of blockchain reporters in order to minimize load the on the ledger and the Bitcoin Wallet Provisioning server.

Once the client determines that its wallet has sufficient funds, the client signs the trasnsaction and makes the PUT /v1/wallet/{paymentId} request to have the appropriate amount transfered to the settlement account with this payload:

<<< PUT /v1/wallet/e43bc29a-7048-486c-b75b-6c4970b4ce2a
<<< content-type: application/json; charset=utf-8
<<< accept-encoding:
<<<
<<< { "surveyorId" : "9czn9x3e26WGmmC09FapUmret1fjupI1lGC6Q7g0H9b"
<<< , "viewingId"  : "f2be4fac-b9de-49b3-95a4-4902a2d5f1d7"
<<< , "signedTx"   : '...'
<<< }
>>> HTTP/1.1 200
>>> cache-control: no-cache
>>> content-type: application/json; charset=utf-8
>>> date: Wed, 10 Aug 2016 03:51:35 GMT
>>> vary: accept-encoding
>>>
>>> { "paymentStamp" : 1470801109946
>>> , "satoshis"     : 845480
>>> , "votes"        : 5
>>> , "hash"         : '...'
>>> }

During the processing of the request, the ledger submits the signed transaction to the Bitcoin Wallet Transaction server. If the transaction is accepted by the server, then a 200 response is returned with an updated paymentStamp property, the number of satoshis transferred, and the corresponding number of votes that are authorized for the viewingId; otherwise, the appropriate 4xx or 5xx response is returned.

On success, the client updates reconcileStamp and adds a new object to the transactions array:

{ "viewingId"    : "f2be4fac-b9de-49b3-95a4-4902a2d5f1d7"
, "surveyorId"   : "9czn9x3e26WGmmC09FapUmret1fjupI1lGC6Q7g0H9b"
, "fee"          :
  { "currency"   : "USD"
  , "amount"     : 5
  }
, "paymentStamp" : 1470801109946
, "satoshis"     : 845480
, "vote"         : 5
}

The client creates a credential for the viewingId making the GET /v1/registrar/viewing request:

<<< GET /v1/registrar/viewing
<<< content-type: application/json; charset=utf-8
<<< accept-encoding:
<<<
>>> HTTP/1.1 200
>>> cache-control: no-cache
>>> content-type: application/json; charset=utf-8
>>> date: Wed, 10 Aug 2016 04:15:49 GMT
>>> vary: accept-encoding
>>>
>>> { "registrarVK" : "..." }

Following this is the POST /v1/registrar/viewing/{viewingId} request:

<<< POST /v1/registrar/viewing/f2be4facb9de9b395a44902a2d5f1d7
<<< content-type: application/json; charset=utf-8
<<< accept-encoding:
<<<
<<< { "proof" : "..." }
>>> HTTP/1.1 200
>>> cache-control: no-cache
>>> content-type: application/json; charset=utf-8
>>> date: Wed, 10 Aug 2016 04:15:50 GMT
>>> vary: accept-encoding
>>>
>>> { "surveyorIds"  :
>>>   [ "87++H/BlRoy1t12WzZ2bNkbNy4RFpkrpb5Dn29Rhd2h"
>>>   , "Cj+w4lEuKU3L0OzrLnhV93WLifEHMhrkrqNevYOW+D9"
>>>   , "75GqrjsWUIv48soMhiiPmyW48Jtn47ffL04o+E6xjAX"
>>>   , "D7EKCnXzVJrnnAIct5a9Y8ev8UdhElqlzKQTfZ/ciYb"
>>>   , "AiGqEJ+b4LEa/7kyFjBvLt4OuQXuCPnYH048mhnr3qD"
>>>   ]
>>> , "verification" : "..."
>>> }

On success, the ledger returns a surveyorIds parameter identifying each of the voting surveyors that the viewingId is authorized to use. After the client verifies the registrar's signature, the client extends the corresponding object in the transaction array with these properties:

{ "credential"  : "{\"userId\":\"f2be4facb9de9b395a44902a2d5f1d7\",...}"
, "surveyorIds" :
  [ "87++H/BlRoy1t12WzZ2bNkbNy4RFpkrpb5Dn29Rhd2h"
  , "Cj+w4lEuKU3L0OzrLnhV93WLifEHMhrkrqNevYOW+D9"
  , "75GqrjsWUIv48soMhiiPmyW48Jtn47ffL04o+E6xjAX"
  , "D7EKCnXzVJrnnAIct5a9Y8ev8UdhElqlzKQTfZ/ciYb"
  , "AiGqEJ+b4LEa/7kyFjBvLt4OuQXuCPnYH048mhnr3qD"
  ]
, "count"       : 0
}

For each entry in the surveyorIds array, the client adds an entry to the ballots array, e.g.:

{ "viewingId"  : "f2be4fac-b9de-49b3-95a4-4902a2d5f1d7"
, "surveyorId" : "87++H/BlRoy1t12WzZ2bNkbNy4RFpkrpb5Dn29Rhd2h"
, "publisher"  : "wikipedia.org"
}

The viewingId property is taken from the corresponding entry in the transactions array, the surveyorId property is taken from the surveyorIds array, and the publisher property is chosen unpredictably (based on the relative percentage that the user has viewed the publisher's site). As each entry is added to the ballots array, the count property in the corresponding entry in the transactions array is incremented.

For each new entry in the ballots array, the client makes the GET /v1/surveyor/voting/{surveyorId}/{viewingId} request to get information about the voting surveyor to be used for this ballot:

<<< GET /v1/surveyor/voting/87%2B%2BH%2FBlRoy1t12WzZ2bNkbNy4RFpkrpb5Dn29Rhd2h/f2be4facb9de9b395a44902a2d5f1d7
<<< content-type: application/json; charset=utf-8
<<< accept-encoding:
<<<
>>> HTTP/1.1 200
>>> cache-control: no-cache
>>> content-type: application/json; charset=utf-8
>>> date: Wed, 10 Aug 2016 04:17:04 GMT
>>> vary: accept-encoding
>>>
>>> { "signature"   : "..."
>>> , "payload"     : {}
>>> , "surveyorId"  : "87++H/BlRoy1t12WzZ2bNkbNy4RFpkrpb5Dn29Rhd2h"
>>> , "surveyVK"    : "..."
>>> , "registrarVK" : "..."
>>> }

This information is then placed into a prepareBallot object and added to the corresponding entry in the ballots array:

  { "signature"   : "..."
  , "payload"     : {}
  , "surveyorId"  : "87++H/BlRoy1t12WzZ2bNkbNy4RFpkrpb5Dn29Rhd2h"
  , "surveyVK"    : "..."
  , "registrarVK" : "..."
  , "server"      :
    { "protocol"  : "https:"
    , ...
    , "href"      : "https://ledger.brave.com/"
    }
  }

The client delays an unpredictable amount of time (e.g., typically within a three-hour period), and then makes the PUT /v1/surveyor/voting/{surveyorId} request with a payload parameter containing the publisher receiving the vote for the associated contribution surveyor:

<<< PUT /v1/surveyor/voting/87%2B%2BH%2FBlRoy1t12WzZ2bNkbNy4RFpkrpb5Dn29Rhd2h
<<< content-type: application/json; charset=utf-8
<<< accept-encoding:
<<<
<<< { "proof" : "{\"publisher\":\"wikipedia.org\"}\n..." }
>>> HTTP/1.1 200
>>> cache-control: no-cache
>>> content-type: application/json; charset=utf-8
>>> date: Wed, 10 Aug 2016 04:57:02 GMT
>>> vary: accept-encoding
>>>
>>> { "submissionId" : "..." }

How Contributions are Processed

to be filled-in in the next draft

Operational Security

Correlating Surveyor Operations

When the client performs the GET /v1/surveyor/{surveyorType}/{surveyorId}/{uID} operation, it necessarily identifies itself to the surveyor in order to receive a secret that authorizes it to anonymously perform the PUT /v1/surveyor/{surveyortype}/{surveyorId} operation.

However, there are two ways that Brave Software or a third-party observer can correlate the operations: timing and IP addresses.

To avoid correlation via timing, the steps above indicate when an unpredictable delay should be interposed by the client when performing the operations.

To avoid corrleation via IP address, the client uses an IP address obfuscation technology to blind the IP address.

Client Wallets

A client wallet is a hierarchical deterministic (HD) wallet, that can be shared partially or entirely with different systems. These HD wallets require 2 of 3 signatures in order to perform funds transfer.

When a client wallet is created, BitGo (the Bitcoin Wallet Provider) keeps one keypair, and the client keeps one keypair (both for signing and for recovery purposes). The third keypair is generated offline for use with the ledger; however, the xprv value is destroyed, and the ledger is given only the xpub value. (Independently, the BitGo service requires an API key from Brave Software in order to process requests.)

Among other things, this gives Brave Software the ability to check the balance independently from the client, but not make withdrawals. In order to make a withdrawal, the client receives an unsigned transaction in response to the GET /v1/wallet/{paymentId} request, and then submits the signed transaction in the payload to the PUTT /v1/wallet/{paymentId} request.

A client wallet is limited as to its 30-day velocity.

If a client loses the key, the user employs the recovery file to provide information to the ledger to transfer funds to a new client wallet.

Brave Settlement Wallets

These wallets are "locked-down" to permit transfers only to other Brave wallets. In turn, these wallets, that are rotated frequently, are used to transfer into publisher and persona wallets.

When created, settlement wallets have their backup key sent to a KRS.

Acknowledgements

Many helpful comments have dramatically increased the simplicity, readability and correctness of this specification. In particular, the comments of Alex Melville of BitGo have been particularly helpful.