Skip to content

Commit e1c2569

Browse files
committed
feat(quickstart): Upgrade to be compatible with BigchainDB Server 0.9
1 parent 6d7c2ac commit e1c2569

File tree

3 files changed

+127
-130
lines changed

3 files changed

+127
-130
lines changed

README.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
# JavaScript quickstart for BigchainDB
22

3-
> :bangbang: High chance of :fire: and :rage: ahead if you expect this to be production-ready
3+
> :bangbang: High chance of :fire: and :rage: ahead if you expect this to be production-ready.
4+
5+
> :bangbang: **ONLY** (and I mean **_only_**) supports BigchainDB Server 0.9
46
57
Some naive helpers to get you on your way to making some transactions :boom:, if you'd like to use
6-
JS with [BigchainDB](https://github.com/bigchaindb/bigchaindb).
8+
[BigchainDB](https://github.com/bigchaindb/bigchaindb) with JavaScript.
79

810
Aimed to support usage in browsers or node, but like every piece of :poop:, it might not. Use at
9-
your own risk :rocket:. At least I can tell you it's ES6, so you'll probably need a babel here and a
10-
bundler there, of which I expect you'll know quite well ([otherwise, go check out js-reactor :wink:](https://github.com/bigchaindb/js-reactor)).
11+
your own risk :rocket:. At least I can tell you it's ES∞+, so you'll probably need a babel here and
12+
a bundler there, of which I expect you'll know quite well ([otherwise, go check out js-reactor
13+
:wink:](https://github.com/bigchaindb/js-reactor)).
1114

1215
## Getting started
1316

@@ -18,7 +21,8 @@ The expected flow for making transactions:
1821
1. Go get yourself some keypairs! Just make a `new Keypair()` (or a whole bunch of them, nobody's
1922
counting :sunglasses:).
2023
1. Go get yourself a condition! `makeEd25519Condition()` should do the trick :sparkles:.
21-
1. Go get a fulfillment (don't worry about the *why*)! `makeEd25519Fulfillment()` no sweat :muscle:.
24+
1. Go wrap that condition as an output (don't worry about the *why*)! `makeOutput()` no sweat
25+
:muscle:.
2226
1. (**Optional**) You've got everyting you need, except for an asset. Maybe define one (any
2327
JSON-serializable object will do).
2428
1. Time to get on the rocket ship, baby. `makeCreateTransaction()` your way to lifelong glory and
@@ -33,13 +37,13 @@ The expected flow for making transactions:
3337
Alright, alright, so you've made a couple transactions. Now what? Do I hear you saying
3438
"<sub>Transfer them??</sub>" No problem, brotha, I gotcha covered :neckbeard:.
3539

36-
1. Go get some more conditions and fulfillments, making sure you create fulfillments to *fulfill* a
37-
previous transaction's condition (maybe you wanna go check out [this](https://docs.bigchaindb.com/projects/server/en/latest/data-models/crypto-conditions.html)
40+
1. Go get some more outputs (wrapping conditions), maybe based on some new made-up friends (i.e.
41+
keypairs).
42+
1. Go make a transfer transaction, using the transaction you want to *spend* (i.e. you can fulfill)
43+
in `makeTransferTransaction()` :v:. *If you're not sure what any of this means (and you're as
44+
confused as I think you are right now), you might wanna go check out [this](https://docs.bigchaindb.com/projects/server/en/latest/data-models/crypto-conditions.html)
3845
and [this](https://docs.bigchaindb.com/projects/py-driver/en/latest/usage.html#asset-transfer)
39-
and [this](https://tools.ietf.org/html/draft-thomas-crypto-conditions-01) if you're as confused
40-
as I think you are).
41-
1. Go make a transfer transaction, using the transaction you want to *spend* in
42-
`makeTransferTransaction()` :v:.
46+
and [this](https://tools.ietf.org/html/draft-thomas-crypto-conditions-01) first.*
4347
1. Sign that transaction with `signTransaction()`!
4448
1. `POST` to the server, and watch the :dollar:s drop, man.
4549

@@ -48,7 +52,7 @@ Alright, alright, so you've made a couple transactions. Now what? Do I hear you
4852
This implementation plays "safe" by using JS-native (or downgradable) libraries for its
4953
crypto-related functions to keep compatabilities with the browser. If that makes you :unamused: and
5054
you'd rather go :godmode: with some :zap: :zap:, you can try using some of these to go as fast as a
51-
:speedboat::
55+
:speedboat: --:surfing_man: :
5256

5357
* [chloride](https://github.com/dominictarr/chloride), or its underlying [sodium](https://github.com/paixaop/node-sodium)
5458
library

index.js

Lines changed: 110 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ import cc from 'five-bells-condition';
66
import nacl from 'tweetnacl';
77
import sha3 from 'js-sha3';
88
import stableStringify from 'json-stable-stringify';
9-
import uuid from 'uuid';
109

1110
/**
1211
* @class Keypair Ed25519 keypair in base58 (as BigchainDB expects base58 keys)
1312
* @type {Object}
1413
* @property {string} publicKey
1514
* @property {string} privateKey
1615
*/
17-
export function Keypair() {
16+
export function Ed25519Keypair() {
1817
const keyPair = nacl.sign.keyPair();
1918
this.publicKey = base58.encode(keyPair.publicKey);
2019

@@ -23,10 +22,9 @@ export function Keypair() {
2322
}
2423

2524
/**
26-
* Create an Ed25519 Cryptocondition from an Ed25519 public key to put into a transaction
27-
* @param {string} publicKey base58 encoded Ed25519 public key for the new "owner"
28-
* @returns {object} Ed25519 Condition in a format compatible with BigchainDB
29-
* Note: Assumes that 'cid' will be adjusted afterwards.
25+
* Create an Ed25519 Cryptocondition from an Ed25519 public key to put into an Output of a Transaction
26+
* @param {string} publicKey base58 encoded Ed25519 public key for the recipient of the Transaction
27+
* @returns {object} Ed25519 Condition (that will need to wrapped in an Output)
3028
*/
3129
export function makeEd25519Condition(publicKey) {
3230
const publicKeyBuffer = new Buffer(base58.decode(publicKey));
@@ -36,122 +34,123 @@ export function makeEd25519Condition(publicKey) {
3634
const conditionUri = ed25519Fulfillment.getConditionUri();
3735

3836
return {
39-
'amount': 1,
40-
'condition': {
41-
'cid': 0, // Will be adjusted after adding the condition to the transaction
42-
'owners_after': [publicKey],
43-
'uri': conditionUri,
44-
'details': {
45-
'signature': null,
46-
'type_id': 4,
47-
'type': 'fulfillment',
48-
'bitmask': 32,
49-
'public_key': publicKey,
50-
},
37+
'details': {
38+
'signature': null,
39+
'type_id': 4,
40+
'type': 'fulfillment',
41+
'bitmask': 32,
42+
'public_key': publicKey,
5143
},
44+
'uri': conditionUri,
5245
};
5346
}
5447

5548
/**
56-
* Create an "empty" Ed25519 fulfillment from a ED25519 public key to put into a transaction.
57-
* This "mock" step is necessary in order for a transaction to be completely out so it can later
58-
* be serialized and signed.
59-
* @param {string} publicKey base58 encoded Ed25519 public key for the previous "owner"
60-
* @returns {object} Ed25519 Condition in a format compatible with BigchainDB
61-
* Note: Assumes that 'cid' will be adjusted afterwards.
49+
* Create an Output from a Condition.
50+
* Note: Assumes the given Condition was generated from a single public key (e.g. a Ed25519 Condition)
51+
* @param {object} condition Condition (e.g. a Ed25519 Condition from `makeEd25519Condition()`)
52+
* @param {number} amount Amount of the output
53+
* @returns {object} An Output usable in a Transaction
6254
*/
63-
export function makeEd25519Fulfillment(publicKey) {
55+
export function makeOutput(condition, amount = 1) {
6456
return {
65-
'owners_before': [publicKey],
66-
'fid': 0, // Will be adjusted after adding the fulfillment to the transaction
67-
'input': null, // Will be filled out after adding the fulfillment to the transaction
68-
'fulfillment': null, // Will be generated during signing
57+
amount,
58+
condition,
59+
'public_keys': [condition.details.public_key],
6960
};
7061
}
7162

7263
/**
73-
* Generate a `CREATE` transaction holding the `assetData`, `metaData`, `conditions`, and
74-
* `fulfillments`.
75-
* @param {object} assetData Asset's `data` property
76-
* @param {object=} metaData Metadata's `data` property
77-
* @param {object[]=} conditions Array of condition objectss to add to the transaction.
78-
* Think of these as the new "owners" of the asset after the transaction.
79-
* For `CREATE` transactions, this should usually just be an Ed25519
80-
* Condition generated from the creator's public key.
81-
* @param {object[]=} fulfillments Array of fulfillment objects to add to the transaction
82-
* Think of these as proofs that you can manipulate the asset.
83-
* For `CREATE` transactions, this should usually just be an
84-
* Ed25519 Fulfillment generated from the creator's public key.
64+
* Generate a `CREATE` transaction holding the `asset`, `metadata`, and `outputs`, to be signed by
65+
* the `issuers`.
66+
* @param {object} asset Created asset's data
67+
* @param {object} metadata Metadata for the Transaction
68+
* @param {object[]} outputs Array of Output objects to add to the Transaction.
69+
* Think of these as the recipients of the asset after the transaction.
70+
* For `CREATE` Transactions, this should usually just be a list of
71+
* Outputs wrapping Ed25519 Conditions generated from the issuers' public
72+
* keys (so that the issuers are the recipients of the created asset).
73+
* @param {...string[]} issuers Public key of one or more issuers to the asset being created by this
74+
* Transaction.
75+
* Note: Each of the private keys corresponding to the given public
76+
* keys MUST be used later (and in the same order) when signing the
77+
* Transaction (`signTransaction()`).
8578
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
8679
* sending it off!
8780
*/
88-
export function makeCreateTransaction(assetData, metadata, conditions, fulfillments) {
89-
const asset = {
90-
'id': uuid.v4(),
91-
'data': assetData || null,
92-
'divisible': false,
93-
'updatable': false,
94-
'refillable': false,
81+
export function makeCreateTransaction(asset, metadata, outputs, ...issuers) {
82+
const assetDefinition = {
83+
'data': asset || null,
9584
};
85+
const inputs = issuers.map((issuer) => makeInputTemplate([issuer]));
9686

97-
return makeTransaction('CREATE', asset, metadata, conditions, fulfillments);
87+
return makeTransaction('CREATE', assetDefinition, metadata, outputs, inputs);
9888
}
9989

10090
/**
101-
* Generate a `TRANSFER` transaction holding the `assetData`, `metaData`, `conditions`, and
102-
* `fulfillments`.
103-
* @param {object} unspentTransaction Transaction you have control over (i.e. can fulfill its
104-
* Condition).
105-
* @param {object=} metaData Metadata's `data` property
106-
* @param {object[]=} conditions Array of condition objects to add to the transaction
107-
* Think of these as the new "owners" of the asset after the transaction.
108-
* For `TRANSFER` transactions, this should usually just be an
109-
* Ed25519 Condition generated from the new owner's public key.
110-
* @param {object[]=} fulfillments Array of fulfillment objects to add to the transaction
111-
* Think of these as proofs that you can manipulate the asset.
112-
* For `TRANSFER` transactions, this should usually just be an
113-
* Ed25519 Fulfillment generated from the creator's public key.
91+
* Generate a `TRANSFER` transaction holding the `asset`, `metadata`, and `outputs`, that fulfills
92+
* the `fulfilledOutputs` of `unspentTransaction`.
93+
* @param {object} unspentTransaction Previous Transaction you have control over (i.e. can fulfill
94+
* its Output Condition)
95+
* @param {object} metadata Metadata for the Transaction
96+
* @param {object[]} outputs Array of Output objects to add to the Transaction.
97+
* Think of these as the recipients of the asset after the transaction.
98+
* For `TRANSFER` Transactions, this should usually just be a list of
99+
* Outputs wrapping Ed25519 Conditions generated from the public keys of
100+
* the recipients.
101+
* @param {...number} fulfilledOutputs Indices of the Outputs in `unspentTransaction` that this
102+
* Transaction fulfills.
103+
* Note that the public keys listed in the fulfilled Outputs
104+
* must be used (and in the same order) to sign the Transaction
105+
* (`signTransaction()`).
114106
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
115107
* sending it off!
116108
*/
117-
export function makeTransferTransaction(unspentTransaction, metadata, conditions, fulfillments) {
118-
// Add transactionLinks to link fulfillments with previous transaction's conditions
119-
// NOTE: Naively assumes that fulfillments are given in the same order as the conditions they're
120-
// meant to fulfill
121-
fulfillments.forEach((fulfillment, index) => {
122-
fulfillment.input = {
123-
'cid': index,
109+
export function makeTransferTransaction(unspentTransaction, metadata, outputs, ...fulfilledOutputs) {
110+
const inputs = fulfilledOutputs.map((outputIndex) => {
111+
const fulfilledOutput = unspentTransaction.outputs[outputIndex];
112+
const transactionLink = {
113+
'output': outputIndex,
124114
'txid': unspentTransaction.id,
125115
};
116+
117+
return makeInputTemplate(fulfilledOutput.public_keys, transactionLink);
126118
});
127119

128-
const assetLink = { 'id': unspentTransaction.transaction.asset.id };
120+
const assetLink = {
121+
'id': unspentTransaction.operation === 'CREATE' ? unspentTransaction.id
122+
: unspentTransaction.asset.id
123+
};
129124

130-
return makeTransaction('TRANSFER', assetLink, metadata, conditions, fulfillments);
125+
return makeTransaction('TRANSFER', assetLink, metadata, outputs, inputs);
131126
}
132127

133128
/**
134-
* Sign a transaction with the given `privateKey`s.
135-
* @param {object} transaction Transaction to sign
136-
* @param {...string} privateKeys base58 Ed25519 private keys.
137-
* Looped through once to iteratively sign any Fulfillments found in
129+
* Sign the given `transaction` with the given `privateKey`s, returning a new copy of `transaction`
130+
* that's been signed.
131+
* Note: Only generates Ed25519 Fulfillments. Thresholds and other types of Fulfillments are left as
132+
* an exercise for the user.
133+
* @param {object} transaction Transaction to sign. `transaction` is not modified.
134+
* @param {...string} privateKeys Private keys associated with the issuers of the `transaction`.
135+
* Looped through to iteratively sign any Input Fulfillments found in
138136
* the `transaction`.
139-
* @returns {object} The original transaction, signed in-place.
137+
* @returns {object} The signed version of `transaction`.
140138
*/
141139
export function signTransaction(transaction, ...privateKeys) {
142-
transaction.transaction.fulfillments.forEach((fulfillment, index) => {
140+
const signedTx = clone(transaction);
141+
signedTx.inputs.forEach((input, index) => {
143142
const privateKey = privateKeys[index];
144143
const privateKeyBuffer = new Buffer(base58.decode(privateKey));
145-
const seriailizedTransaction = serializeTransactionWithoutFulfillments(transaction);
144+
const serializedTransaction = serializeTransactionIntoCanonicalString(transaction);
146145

147146
const ed25519Fulfillment = new cc.Ed25519();
148-
ed25519Fulfillment.sign(new Buffer(seriailizedTransaction), privateKeyBuffer);
147+
ed25519Fulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer);
149148
const fulfillmentUri = ed25519Fulfillment.serializeUri();
150149

151-
fulfillment.fulfillment = fulfillmentUri;
150+
input.fulfillment = fulfillmentUri;
152151
});
153152

154-
return transaction;
153+
return signedTx;
155154
}
156155

157156
/*********************
@@ -161,39 +160,32 @@ export function signTransaction(transaction, ...privateKeys) {
161160
function makeTransactionTemplate() {
162161
return {
163162
'id': null,
164-
'version': 1,
165-
'transaction': {
166-
'operation': null,
167-
'conditions': [],
168-
'fulfillments': [],
169-
'metadata': null,
170-
'asset': null,
171-
},
163+
'operation': null,
164+
'outputs': [],
165+
'inputs': [],
166+
'metadata': null,
167+
'asset': null,
168+
'version': '0.9',
169+
};
170+
}
171+
172+
function makeInputTemplate(publicKeys = [], fulfills = null, fulfillment = null) {
173+
return {
174+
fulfillment,
175+
fulfills,
176+
'owners_before': publicKeys,
172177
};
173178
}
174179

175-
function makeTransaction(operation, asset, metadata, conditions = [], fulfillments = []) {
180+
function makeTransaction(operation, asset, metadata = null, outputs = [], inputs = []) {
176181
const tx = makeTransactionTemplate();
177182
tx.operation = operation;
178-
tx.transaction.asset = asset;
179-
180-
if (metadata) {
181-
tx.transaction.metadata = {
182-
'id': uuid.v4(),
183-
'data': metadata,
184-
};
185-
}
186-
187-
tx.transaction.conditions.push(...conditions);
188-
tx.transaction.conditions.forEach((condition, index) => {
189-
condition.cid = index;
190-
});
191-
192-
tx.transaction.fulfillments.push(...fulfillments);
193-
tx.transaction.fulfillments.forEach((fulfillment, index) => {
194-
fulfillment.fid = index;
195-
});
183+
tx.asset = asset;
184+
tx.metadata = metadata;
185+
tx.inputs = inputs;
186+
tx.outputs = outputs;
196187

188+
// Hashing must be done after, as the hash is of the Transaction (up to now)
197189
tx.id = hashTransaction(tx);
198190
return tx;
199191
}
@@ -203,7 +195,11 @@ function makeTransaction(operation, asset, metadata, conditions = [], fulfillmen
203195
****************/
204196

205197
function hashTransaction(transaction) {
206-
return sha256Hash(serializeTransactionWithoutFulfillments(transaction));
198+
// Safely remove any tx id from the given transaction for hashing
199+
const tx = { ...transaction };
200+
delete tx.id;
201+
202+
return sha256Hash(serializeTransactionIntoCanonicalString(tx));
207203
}
208204

209205
function sha256Hash(data) {
@@ -213,14 +209,12 @@ function sha256Hash(data) {
213209
.hex();
214210
}
215211

216-
function serializeTransactionWithoutFulfillments(transaction) {
217-
// BigchainDB creates transactions IDs and signs fulfillments by serializing transactions
218-
// into a "canonical" format where the transaction id and each fulfillment URI are ignored and
219-
// the remaining keys are sorted
212+
function serializeTransactionIntoCanonicalString(transaction) {
213+
// BigchainDB signs fulfillments by serializing transactions into a "canonical" format where
214+
// each fulfillment URI is removed before sorting the remaining keys
220215
const tx = clone(transaction);
221-
delete tx.id;
222-
tx.transaction.fulfillments.forEach((fulfillment) => {
223-
fulfillment.fulfillment = null;
216+
tx.inputs.forEach((input) => {
217+
input.fulfillment = null;
224218
});
225219

226220
// Sort the keys

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@
3232
"five-bells-condition": "^3.3.1",
3333
"js-sha3": "^0.5.7",
3434
"json-stable-stringify": "^1.0.1",
35-
"tweetnacl": "^0.14.3",
36-
"uuid": "^3.0.0"
35+
"tweetnacl": "^0.14.3"
3736
},
3837
"keywords": [
3938
"bigchaindb",

0 commit comments

Comments
 (0)