mirror of
https://github.com/bigchaindb/js-bigchaindb-driver.git
synced 2024-11-22 01:36:56 +01:00
Add js tool
This commit is contained in:
commit
6baa42b2f0
18
.babelrc
Normal file
18
.babelrc
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
'presets': [['latest', { es2015: { modules: false } }]],
|
||||
'plugins': [
|
||||
'transform-object-assign',
|
||||
'transform-object-rest-spread',
|
||||
[ 'transform-runtime', {
|
||||
'polyfill': false,
|
||||
'regenerator': false
|
||||
} ]
|
||||
],
|
||||
'sourceMaps': true,
|
||||
|
||||
'env': {
|
||||
'cjs': {
|
||||
'presets': ['latest']
|
||||
}
|
||||
}
|
||||
}
|
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
build/*
|
||||
dist/*
|
||||
node_modules/*
|
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "ascribe"
|
||||
}
|
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
*.seed
|
||||
*.log
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
.idea
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
*.sublime-workspace
|
||||
|
||||
.env
|
||||
|
||||
build/*
|
||||
|
||||
node_modules/*
|
332
README.md
Normal file
332
README.md
Normal file
@ -0,0 +1,332 @@
|
||||
# JavaScript quickstart for BigchainDB
|
||||
|
||||
> :bangbang: High chance of :fire: and :rage: ahead if you expect this to be production-ready.
|
||||
|
||||
> :bangbang: **ONLY** (and I mean **_only_**) supports BigchainDB Server 0.9
|
||||
|
||||
Some naive helpers to get you on your way to making some transactions :boom:, if you'd like to use
|
||||
[BigchainDB](https://github.com/bigchaindb/bigchaindb) with JavaScript.
|
||||
|
||||
Aimed to support usage in browsers or node; if it doesn't, well, I don't know what to say except
|
||||
it's probably you :smirk:. Use at your own risk :rocket:. At least I can tell you it's ES∞+, so
|
||||
you'll probably need a babel here and a bundler there (or use [one of the built versions](./dist)),
|
||||
of which I expect you'll know quite well ([otherwise, go check out js-reactor :wink:](https://github.com/bigchaindb/js-reactor)).
|
||||
|
||||
## Getting started
|
||||
|
||||
Srs, just read through [index.js](./index.js) and see if you can make any sense of it.
|
||||
|
||||
You may also be interested in a [long-form example with actual code](#example).
|
||||
|
||||
The expected flow for making transactions:
|
||||
|
||||
1. Go get yourself some keypairs! Just make a `new Keypair()` (or a whole bunch of them, nobody's
|
||||
counting :sunglasses:).
|
||||
1. Go get yourself a condition! `makeEd25519Condition()` should do the trick :sparkles:.
|
||||
1. Go wrap that condition as an output (don't worry about the *why*)! `makeOutput()` no sweat
|
||||
:muscle:.
|
||||
1. (**Optional**) You've got everyting you need, except for an asset. Maybe define one (any
|
||||
JSON-serializable object will do).
|
||||
1. Time to get on the rocket ship, baby. `makeCreateTransaction()` your way to lifelong glory and
|
||||
fame :clap:!
|
||||
1. Ok, now you've got a transaction, but we need you to *sign* (`signTransaction()`) it cause, you
|
||||
know... cryptography and `¯\_(ツ)_/¯`.
|
||||
1. Alright, sick dude, you've *finally* got everything you need to `POST` to a server. Phew
|
||||
:sweat_drops:. Go `fetch()` your way to business, start:point_up:life4evar!
|
||||
|
||||
...
|
||||
|
||||
Alright, alright, so you've made a couple transactions. Now what? Do I hear you saying
|
||||
"<sub>Transfer them??</sub>" No problem, brotha, I gotcha covered :neckbeard:.
|
||||
|
||||
1. Go get some more outputs (wrapping conditions), maybe based on some new made-up friends (i.e.
|
||||
keypairs).
|
||||
1. Go make a transfer transaction, using the transaction you want to *spend* (i.e. you can fulfill)
|
||||
in `makeTransferTransaction()` :v:. *If you're not sure what any of this means (and you're as
|
||||
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)
|
||||
and [this](https://docs.bigchaindb.com/projects/py-driver/en/latest/usage.html#asset-transfer)
|
||||
and [this](https://tools.ietf.org/html/draft-thomas-crypto-conditions-01) first.*
|
||||
1. Sign that transaction with `signTransaction()`!
|
||||
1. `POST` to the server, and watch the :dollar:s drop, man.
|
||||
|
||||
## Needs for speeds
|
||||
|
||||
This implementation plays "safe" by using JS-native (or downgradable) libraries for its
|
||||
crypto-related functions to keep compatabilities with the browser. If that makes you :unamused: and
|
||||
you'd rather go :godmode: with some :zap: :zap:, you can try using some of these to go as fast as a
|
||||
:speedboat: --:surfing_man: :
|
||||
|
||||
* [chloride](https://github.com/dominictarr/chloride), or its underlying [sodium](https://github.com/paixaop/node-sodium)
|
||||
library
|
||||
* [node-sha3](https://github.com/phusion/node-sha3) -- **MAKE SURE** to use [steakknife's fork](https://github.com/steakknife/node-sha3)
|
||||
if [the FIPS 202 upgrade](https://github.com/phusion/node-sha3/pull/25) hasn't been merged
|
||||
(otherwise, you'll run into all kinds of hashing problems)
|
||||
|
||||
## :rotating_light: WARNING WARNING WARNING :rotating_light:
|
||||
|
||||
> Crypto-conditions
|
||||
|
||||
Make sure you keep using a crypto-conditions implementation that implements the older v1 draft (e.g.
|
||||
[`five-bells-condition@v3.3.1`](https://github.com/interledgerjs/five-bells-condition/releases/tag/v3.3.1)).
|
||||
BigchainDB Server 0.9 does not implement the newer version of the spec and **WILL** fail if you to
|
||||
use a newer implementation of crypto-conditions.
|
||||
|
||||
> SHA3
|
||||
|
||||
Make sure to use a SHA3 implementation that has been upgraded as per [FIPS 202](http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf).
|
||||
Otherwise, the hashes you generate **WILL** be invalid in the eyes of the BigchainDB Server.
|
||||
|
||||
> Ed25519
|
||||
|
||||
If you do end up replacing `tweetnacl` with `chloride` (or any other Ed25519 package), you might
|
||||
want to double check that it gives you a correct public/private (or verifying/signing, if they use
|
||||
that lingo) keypair.
|
||||
|
||||
An example BigchainDB Server-generated keypair (encoded in base58):
|
||||
|
||||
- Public: "DjPMHDD9JtgypDKY38mPz9f6owjAMAKhLuN1JfRAat8C"
|
||||
- Private: "7Gf5YRch2hYTyeLxqNLgTY63D9K5QH2UQ7LYFeBGuKvo"
|
||||
|
||||
Your package should be able to take in the decoded version of the **private** key and return you the
|
||||
same **public** key (once you encode that to base58).
|
||||
|
||||
-------
|
||||
|
||||
## Example
|
||||
|
||||
OK, OK, I gotcha, you'd rather see some *actual* code rather than a giant list of steps that don't
|
||||
mean anything. :point_down: is for you.
|
||||
|
||||
```js
|
||||
import {
|
||||
Ed25519Keypair,
|
||||
makeEd25519Condition,
|
||||
makeOutput,
|
||||
makeCreateTransaction,
|
||||
makeTransferTransaction,
|
||||
signTransaction,
|
||||
} from 'js-bigchaindb-quickstart'; // Or however you'd like to import it
|
||||
|
||||
/**********************
|
||||
* CREATE transaction *
|
||||
**********************/
|
||||
// First, create a keypair for our new friend, Ash (let's be real--who would you rather catch some
|
||||
// Pokemon: Alice or the Ketchum man himself?)
|
||||
const ash = new Ed25519Keypair();
|
||||
|
||||
console.log(ash.publicKey); // something like "DjPMHDD9JtgypDKY38mPz9f6owjAMAKhLuN1JfRAat8C"
|
||||
console.log(ash.privateKey); // something like "7Gf5YRch2hYTyeLxqNLgTY63D9K5QH2UQ7LYFeBGuKvo"
|
||||
|
||||
// Let's get an output and condition that lets Ash be the recipient of the new asset we're creating
|
||||
const ashCondition = new makeEd25519Condition(ash.publicKey);
|
||||
const ashOutput = new makeOutput(ashCondition);
|
||||
|
||||
console.log(ashOutput);
|
||||
/* Something like
|
||||
{
|
||||
"amount": 1,
|
||||
"condition": {
|
||||
"details": {
|
||||
"signature": null,
|
||||
"type_id": 4,
|
||||
"type": "fulfillment",
|
||||
"bitmask": 32,
|
||||
"public_key": "DjPMHDD9JtgypDKY38mPz9f6owjAMAKhLuN1JfRAat8C"
|
||||
},
|
||||
"uri": "cc:4:20:vSfobaaMSP52nxnVkPiLMysCTR-t8JpjbWIdU6SvRYU:96"
|
||||
},
|
||||
"public_keys": [
|
||||
"DjPMHDD9JtgypDKY38mPz9f6owjAMAKhLuN1JfRAat8C"
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
// Let's make an asset, to pretend this isn't boring.
|
||||
const pokeAsset = {
|
||||
'name': 'Pikachu',
|
||||
'trait': 'Will never, ever, EVAARRR leave your back'
|
||||
};
|
||||
|
||||
const noMetadata = null; // Let's ignore that meta-stuff for now
|
||||
|
||||
// Now let's go give Ash his beloved Pikachu
|
||||
const createPokeTx = makeCreateTransaction(pokeAsset, noMetadata, [ashOutput], ash.publicKey);
|
||||
|
||||
console.log(createPokeTx);
|
||||
/* Something like
|
||||
{
|
||||
"id": "38acf7a938a39be335afc8e7300468b981a29813d52938104ba3badfe21470c9",
|
||||
"operation": "CREATE",
|
||||
"outputs": [
|
||||
{
|
||||
"amount": 1,
|
||||
"condition": {
|
||||
"details": {
|
||||
"signature": null,
|
||||
"type_id": 4,
|
||||
"type": "fulfillment",
|
||||
"bitmask": 32,
|
||||
"public_key": "DjPMHDD9JtgypDKY38mPz9f6owjAMAKhLuN1JfRAat8C"
|
||||
},
|
||||
"uri": "cc:4:20:vSfobaaMSP52nxnVkPiLMysCTR-t8JpjbWIdU6SvRYU:96"
|
||||
},
|
||||
"public_keys": [
|
||||
"DjPMHDD9JtgypDKY38mPz9f6owjAMAKhLuN1JfRAat8C"
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"fulfillment": null,
|
||||
"fulfills": null,
|
||||
"owners_before": [
|
||||
"DjPMHDD9JtgypDKY38mPz9f6owjAMAKhLuN1JfRAat8C"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": null,
|
||||
"asset": {
|
||||
"data": {
|
||||
"name": "Pikachu",
|
||||
"trait": "Will never, ever, EVAARRR leave your back"
|
||||
}
|
||||
},
|
||||
"version": "0.9"
|
||||
}
|
||||
*/
|
||||
|
||||
// Let's sign this thing to make it legit! (Let's call Ash the "issuer", but a registered PokeCorp
|
||||
// could be the one issuing instead)
|
||||
const signedCreateTx = signTransaction(createPokeTx, ash.privateKey);
|
||||
|
||||
console.log(signedPokeTx);
|
||||
/* Something like
|
||||
{
|
||||
"id": "38acf7a938a39be335afc8e7300468b981a29813d52938104ba3badfe21470c9",
|
||||
"operation": "CREATE",
|
||||
"outputs": [
|
||||
{
|
||||
"amount": 1,
|
||||
"condition": {
|
||||
"details": {
|
||||
"signature": null,
|
||||
"type_id": 4,
|
||||
"type": "fulfillment",
|
||||
"bitmask": 32,
|
||||
"public_key": "DjPMHDD9JtgypDKY38mPz9f6owjAMAKhLuN1JfRAat8C"
|
||||
},
|
||||
"uri": "cc:4:20:vSfobaaMSP52nxnVkPiLMysCTR-t8JpjbWIdU6SvRYU:96"
|
||||
},
|
||||
"public_keys": [
|
||||
"DjPMHDD9JtgypDKY38mPz9f6owjAMAKhLuN1JfRAat8C"
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"fulfillment": "cf:4:vSfobaaMSP52nxnVkPiLMysCTR-t8JpjbWIdU6SvRYWj-cp1qb1vsTSt_775cGe-NQFxgyUQvcPx1nWkJRgXhMvTk2vN2QJU_nd2DgeTbIcWBF-8-N1SH2WqQLsXJLcP",
|
||||
"fulfills": null,
|
||||
"owners_before": [
|
||||
"DjPMHDD9JtgypDKY38mPz9f6owjAMAKhLuN1JfRAat8C"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": null,
|
||||
"asset": {
|
||||
"data": {
|
||||
"name": "Pikachu",
|
||||
"trait": "Will never, ever, EVAARRR leave your back"
|
||||
}
|
||||
},
|
||||
"version": "0.9"
|
||||
}
|
||||
*/
|
||||
|
||||
// Alright, now you've got yourself a valid transaction and you can do some crazy thing like send it
|
||||
// over to a BigchainDB node. I'll leave that as an exercise for you ;).
|
||||
|
||||
/************************
|
||||
* TRANSFER transaction *
|
||||
************************/
|
||||
// Alright, let's get Ash some imaginary friends (remember Brock? Neither do I)
|
||||
const brock = new Ed25519Keypair(); // public: "H8ZVy61CCKh5VQV9nzzzggNW8e5CyTbSiegpdLqLSmqi", private: "5xoYuPP92pznaGZF9KLsyAdR5C7yDU79of1KA9UK4qKS"
|
||||
|
||||
// Let's pretend that, for the sake of this example, Ash can actually part with Pikachu. Let's trade
|
||||
// Pikachu to Brock (we won't be getting anything back, but if it helps, you can pretend Brock'll
|
||||
// give Ash some help with his love life).
|
||||
const brockCondition = new makeEd25519Condition(brock.publicKey);
|
||||
const brockOutput = new makeOutput(brockCondition);
|
||||
|
||||
// Let's create the TRANSFER transaction cementing this trade. We'll use the "unspent" CREATE
|
||||
// transaction that assigned Pikachu to Ash as an input to this TRANSFER.
|
||||
// Note that we'll keep ignoring that metadata stuff.
|
||||
// Also note that we could use either `createPokeTx` (unsigned) or `signedCreateTx` (signed) here
|
||||
// for the input transaction. Either way, we'll be fulfilling the first (and only) output set in it.
|
||||
const fulfilledOutputIndex = 0;
|
||||
const transferPokeTx = makeTransferTransaction(createPokeTx, noMetadata, [brockOutput], fulfilledOutputIndex);
|
||||
|
||||
// OK, let's sign this TRANSFER (Ash has to, as he's the one currently in "control" of Pikachu)
|
||||
const signedTransferTx = signTransaction(transferPokeTx, ash.privateKey);
|
||||
|
||||
console.log(signedTransferTx);
|
||||
/* If everything went well, you should get something like this
|
||||
{
|
||||
"id": "0876962a40479e171135cd92dbae7f0216f2691561b56a579cff631371d4d128",
|
||||
"operation": "TRANSFER",
|
||||
"outputs": [
|
||||
{
|
||||
"amount": 1,
|
||||
"condition": {
|
||||
"details": {
|
||||
"signature": null,
|
||||
"type_id": 4,
|
||||
"type": "fulfillment",
|
||||
"bitmask": 32,
|
||||
"public_key": "H8ZVy61CCKh5VQV9nzzzggNW8e5CyTbSiegpdLqLSmqi"
|
||||
},
|
||||
"uri": "cc:4:20:76rNv-DAIjZC0-68Gl0KEuDpcJRpCAAQXxvVbTvQAxE:96"
|
||||
},
|
||||
"public_keys": [
|
||||
"H8ZVy61CCKh5VQV9nzzzggNW8e5CyTbSiegpdLqLSmqi"
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"fulfillment": "cf:4:vSfobaaMSP52nxnVkPiLMysCTR-t8JpjbWIdU6SvRYU8UJKi0Oq7QoCXIHuiWEYzxfgVEYs9HHtDIWBSkq1uvMX6l7VKwUCrK93k6JMNVBA8djOa5UGfDDF49xLVEgQI",
|
||||
"fulfills": {
|
||||
"output": 0,
|
||||
"txid": "38acf7a938a39be335afc8e7300468b981a29813d52938104ba3badfe21470c9"
|
||||
},
|
||||
"owners_before": [
|
||||
"DjPMHDD9JtgypDKY38mPz9f6owjAMAKhLuN1JfRAat8C"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": null,
|
||||
"asset": {
|
||||
"id": "38acf7a938a39be335afc8e7300468b981a29813d52938104ba3badfe21470c9"
|
||||
},
|
||||
"version": "0.9"
|
||||
}
|
||||
*/
|
||||
|
||||
// Assuming you figured out how to send a transaction to a BigchainDB node, and that the federation
|
||||
// you sent it to has validated the CREATE transaction you sent, you should now be able to cement
|
||||
// the TRANSFER of Pikachu to Brock by sending `signedTransferTx` to a node in the same federation.
|
||||
|
||||
|
||||
|
||||
=========================================================================================================
|
||||
|
||||
|
||||
|
||||
/*************************************************************
|
||||
* *
|
||||
* ~~~ CHALLENGE ~~~ *
|
||||
* *
|
||||
* So who's making the decentralized version of Pokemon? *
|
||||
* (cause I want in) *
|
||||
* *
|
||||
*************************************************************/
|
||||
```
|
12
dist/README.md
vendored
Normal file
12
dist/README.md
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# Built files, for your convenience
|
||||
|
||||
~Voila~ ma boies, built files so you don't have to do a thing! Just copy pasta, and get on your way.
|
||||
|
||||
### `/bundled`
|
||||
|
||||
Babelified, packaged with dependencies, and built (if you'd like), so you can drop it in anywhere
|
||||
you want.
|
||||
|
||||
### `/node`
|
||||
|
||||
Babelified into a CommonJS module, so you can drop it in on any node project.
|
6964
dist/bundle/bundle.js
vendored
Normal file
6964
dist/bundle/bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
28
dist/bundle/bundle.min.js
vendored
Normal file
28
dist/bundle/bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/bundle/bundle.min.js.map
vendored
Normal file
1
dist/bundle/bundle.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
281
dist/node/index.js
vendored
Normal file
281
dist/node/index.js
vendored
Normal file
@ -0,0 +1,281 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _extends2 = require('babel-runtime/helpers/extends');
|
||||
|
||||
var _extends3 = _interopRequireDefault(_extends2);
|
||||
|
||||
exports.Ed25519Keypair = Ed25519Keypair;
|
||||
exports.makeEd25519Condition = makeEd25519Condition;
|
||||
exports.makeOutput = makeOutput;
|
||||
exports.makeCreateTransaction = makeCreateTransaction;
|
||||
exports.makeTransferTransaction = makeTransferTransaction;
|
||||
exports.signTransaction = signTransaction;
|
||||
|
||||
var _buffer = require('buffer');
|
||||
|
||||
var _bs = require('bs58');
|
||||
|
||||
var _bs2 = _interopRequireDefault(_bs);
|
||||
|
||||
var _clone = require('clone');
|
||||
|
||||
var _clone2 = _interopRequireDefault(_clone);
|
||||
|
||||
var _fiveBellsCondition = require('five-bells-condition');
|
||||
|
||||
var _fiveBellsCondition2 = _interopRequireDefault(_fiveBellsCondition);
|
||||
|
||||
var _tweetnacl = require('tweetnacl');
|
||||
|
||||
var _tweetnacl2 = _interopRequireDefault(_tweetnacl);
|
||||
|
||||
var _jsSha = require('js-sha3');
|
||||
|
||||
var _jsSha2 = _interopRequireDefault(_jsSha);
|
||||
|
||||
var _jsonStableStringify = require('json-stable-stringify');
|
||||
|
||||
var _jsonStableStringify2 = _interopRequireDefault(_jsonStableStringify);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
/**
|
||||
* @class Keypair Ed25519 keypair in base58 (as BigchainDB expects base58 keys)
|
||||
* @type {Object}
|
||||
* @property {string} publicKey
|
||||
* @property {string} privateKey
|
||||
*/
|
||||
function Ed25519Keypair() {
|
||||
var keyPair = _tweetnacl2.default.sign.keyPair();
|
||||
this.publicKey = _bs2.default.encode(keyPair.publicKey);
|
||||
|
||||
// tweetnacl's generated secret key is the secret key + public key (resulting in a 64-byte buffer)
|
||||
this.privateKey = _bs2.default.encode(keyPair.secretKey.slice(0, 32));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Ed25519 Cryptocondition from an Ed25519 public key to put into an Output of a Transaction
|
||||
* @param {string} publicKey base58 encoded Ed25519 public key for the recipient of the Transaction
|
||||
* @returns {object} Ed25519 Condition (that will need to wrapped in an Output)
|
||||
*/
|
||||
function makeEd25519Condition(publicKey) {
|
||||
var publicKeyBuffer = new _buffer.Buffer(_bs2.default.decode(publicKey));
|
||||
|
||||
var ed25519Fulfillment = new _fiveBellsCondition2.default.Ed25519();
|
||||
ed25519Fulfillment.setPublicKey(publicKeyBuffer);
|
||||
var conditionUri = ed25519Fulfillment.getConditionUri();
|
||||
|
||||
return {
|
||||
'details': {
|
||||
'signature': null,
|
||||
'type_id': 4,
|
||||
'type': 'fulfillment',
|
||||
'bitmask': 32,
|
||||
'public_key': publicKey
|
||||
},
|
||||
'uri': conditionUri
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Output from a Condition.
|
||||
* Note: Assumes the given Condition was generated from a single public key (e.g. a Ed25519 Condition)
|
||||
* @param {object} condition Condition (e.g. a Ed25519 Condition from `makeEd25519Condition()`)
|
||||
* @param {number} amount Amount of the output
|
||||
* @returns {object} An Output usable in a Transaction
|
||||
*/
|
||||
function makeOutput(condition) {
|
||||
var amount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
|
||||
|
||||
return {
|
||||
amount: amount,
|
||||
condition: condition,
|
||||
'public_keys': [condition.details.public_key]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a `CREATE` transaction holding the `asset`, `metadata`, and `outputs`, to be signed by
|
||||
* the `issuers`.
|
||||
* @param {object} asset Created asset's data
|
||||
* @param {object} metadata Metadata for the Transaction
|
||||
* @param {object[]} outputs Array of Output objects to add to the Transaction.
|
||||
* Think of these as the recipients of the asset after the transaction.
|
||||
* For `CREATE` Transactions, this should usually just be a list of
|
||||
* Outputs wrapping Ed25519 Conditions generated from the issuers' public
|
||||
* keys (so that the issuers are the recipients of the created asset).
|
||||
* @param {...string[]} issuers Public key of one or more issuers to the asset being created by this
|
||||
* Transaction.
|
||||
* Note: Each of the private keys corresponding to the given public
|
||||
* keys MUST be used later (and in the same order) when signing the
|
||||
* Transaction (`signTransaction()`).
|
||||
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
|
||||
* sending it off!
|
||||
*/
|
||||
function makeCreateTransaction(asset, metadata, outputs) {
|
||||
var assetDefinition = {
|
||||
'data': asset || null
|
||||
};
|
||||
|
||||
for (var _len = arguments.length, issuers = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
|
||||
issuers[_key - 3] = arguments[_key];
|
||||
}
|
||||
|
||||
var inputs = issuers.map(function (issuer) {
|
||||
return makeInputTemplate([issuer]);
|
||||
});
|
||||
|
||||
return makeTransaction('CREATE', assetDefinition, metadata, outputs, inputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a `TRANSFER` transaction holding the `asset`, `metadata`, and `outputs`, that fulfills
|
||||
* the `fulfilledOutputs` of `unspentTransaction`.
|
||||
* @param {object} unspentTransaction Previous Transaction you have control over (i.e. can fulfill
|
||||
* its Output Condition)
|
||||
* @param {object} metadata Metadata for the Transaction
|
||||
* @param {object[]} outputs Array of Output objects to add to the Transaction.
|
||||
* Think of these as the recipients of the asset after the transaction.
|
||||
* For `TRANSFER` Transactions, this should usually just be a list of
|
||||
* Outputs wrapping Ed25519 Conditions generated from the public keys of
|
||||
* the recipients.
|
||||
* @param {...number} fulfilledOutputs Indices of the Outputs in `unspentTransaction` that this
|
||||
* Transaction fulfills.
|
||||
* Note that the public keys listed in the fulfilled Outputs
|
||||
* must be used (and in the same order) to sign the Transaction
|
||||
* (`signTransaction()`).
|
||||
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
|
||||
* sending it off!
|
||||
*/
|
||||
function makeTransferTransaction(unspentTransaction, metadata, outputs) {
|
||||
for (var _len2 = arguments.length, fulfilledOutputs = Array(_len2 > 3 ? _len2 - 3 : 0), _key2 = 3; _key2 < _len2; _key2++) {
|
||||
fulfilledOutputs[_key2 - 3] = arguments[_key2];
|
||||
}
|
||||
|
||||
var inputs = fulfilledOutputs.map(function (outputIndex) {
|
||||
var fulfilledOutput = unspentTransaction.outputs[outputIndex];
|
||||
var transactionLink = {
|
||||
'output': outputIndex,
|
||||
'txid': unspentTransaction.id
|
||||
};
|
||||
|
||||
return makeInputTemplate(fulfilledOutput.public_keys, transactionLink);
|
||||
});
|
||||
|
||||
var assetLink = {
|
||||
'id': unspentTransaction.operation === 'CREATE' ? unspentTransaction.id : unspentTransaction.asset.id
|
||||
};
|
||||
|
||||
return makeTransaction('TRANSFER', assetLink, metadata, outputs, inputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the given `transaction` with the given `privateKey`s, returning a new copy of `transaction`
|
||||
* that's been signed.
|
||||
* Note: Only generates Ed25519 Fulfillments. Thresholds and other types of Fulfillments are left as
|
||||
* an exercise for the user.
|
||||
* @param {object} transaction Transaction to sign. `transaction` is not modified.
|
||||
* @param {...string} privateKeys Private keys associated with the issuers of the `transaction`.
|
||||
* Looped through to iteratively sign any Input Fulfillments found in
|
||||
* the `transaction`.
|
||||
* @returns {object} The signed version of `transaction`.
|
||||
*/
|
||||
function signTransaction(transaction) {
|
||||
for (var _len3 = arguments.length, privateKeys = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
|
||||
privateKeys[_key3 - 1] = arguments[_key3];
|
||||
}
|
||||
|
||||
var signedTx = (0, _clone2.default)(transaction);
|
||||
signedTx.inputs.forEach(function (input, index) {
|
||||
var privateKey = privateKeys[index];
|
||||
var privateKeyBuffer = new _buffer.Buffer(_bs2.default.decode(privateKey));
|
||||
var serializedTransaction = serializeTransactionIntoCanonicalString(transaction);
|
||||
|
||||
var ed25519Fulfillment = new _fiveBellsCondition2.default.Ed25519();
|
||||
ed25519Fulfillment.sign(new _buffer.Buffer(serializedTransaction), privateKeyBuffer);
|
||||
var fulfillmentUri = ed25519Fulfillment.serializeUri();
|
||||
|
||||
input.fulfillment = fulfillmentUri;
|
||||
});
|
||||
|
||||
return signedTx;
|
||||
}
|
||||
|
||||
/*********************
|
||||
* Transaction utils *
|
||||
*********************/
|
||||
|
||||
function makeTransactionTemplate() {
|
||||
return {
|
||||
'id': null,
|
||||
'operation': null,
|
||||
'outputs': [],
|
||||
'inputs': [],
|
||||
'metadata': null,
|
||||
'asset': null,
|
||||
'version': '0.9'
|
||||
};
|
||||
}
|
||||
|
||||
function makeInputTemplate() {
|
||||
var publicKeys = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
||||
var fulfills = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
||||
var fulfillment = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
||||
|
||||
return {
|
||||
fulfillment: fulfillment,
|
||||
fulfills: fulfills,
|
||||
'owners_before': publicKeys
|
||||
};
|
||||
}
|
||||
|
||||
function makeTransaction(operation, asset) {
|
||||
var metadata = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
||||
var outputs = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
|
||||
var inputs = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
|
||||
|
||||
var tx = makeTransactionTemplate();
|
||||
tx.operation = operation;
|
||||
tx.asset = asset;
|
||||
tx.metadata = metadata;
|
||||
tx.inputs = inputs;
|
||||
tx.outputs = outputs;
|
||||
|
||||
// Hashing must be done after, as the hash is of the Transaction (up to now)
|
||||
tx.id = hashTransaction(tx);
|
||||
return tx;
|
||||
}
|
||||
|
||||
/****************
|
||||
* Crypto utils *
|
||||
****************/
|
||||
|
||||
function hashTransaction(transaction) {
|
||||
// Safely remove any tx id from the given transaction for hashing
|
||||
var tx = (0, _extends3.default)({}, transaction);
|
||||
delete tx.id;
|
||||
|
||||
return sha256Hash(serializeTransactionIntoCanonicalString(tx));
|
||||
}
|
||||
|
||||
function sha256Hash(data) {
|
||||
return _jsSha2.default.sha3_256.create().update(data).hex();
|
||||
}
|
||||
|
||||
function serializeTransactionIntoCanonicalString(transaction) {
|
||||
// BigchainDB signs fulfillments by serializing transactions into a "canonical" format where
|
||||
// each fulfillment URI is removed before sorting the remaining keys
|
||||
var tx = (0, _clone2.default)(transaction);
|
||||
tx.inputs.forEach(function (input) {
|
||||
input.fulfillment = null;
|
||||
});
|
||||
|
||||
// Sort the keys
|
||||
return (0, _jsonStableStringify2.default)(tx, function (a, b) {
|
||||
return a.key > b.key ? 1 : -1;
|
||||
});
|
||||
}
|
222
index.js
Normal file
222
index.js
Normal file
@ -0,0 +1,222 @@
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
import base58 from 'bs58';
|
||||
import clone from 'clone';
|
||||
import cc from 'five-bells-condition';
|
||||
import nacl from 'tweetnacl';
|
||||
import sha3 from 'js-sha3';
|
||||
import stableStringify from 'json-stable-stringify';
|
||||
|
||||
/**
|
||||
* @class Keypair Ed25519 keypair in base58 (as BigchainDB expects base58 keys)
|
||||
* @type {Object}
|
||||
* @property {string} publicKey
|
||||
* @property {string} privateKey
|
||||
*/
|
||||
export function Ed25519Keypair() {
|
||||
const keyPair = nacl.sign.keyPair();
|
||||
this.publicKey = base58.encode(keyPair.publicKey);
|
||||
|
||||
// tweetnacl's generated secret key is the secret key + public key (resulting in a 64-byte buffer)
|
||||
this.privateKey = base58.encode(keyPair.secretKey.slice(0, 32));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Ed25519 Cryptocondition from an Ed25519 public key to put into an Output of a Transaction
|
||||
* @param {string} publicKey base58 encoded Ed25519 public key for the recipient of the Transaction
|
||||
* @returns {object} Ed25519 Condition (that will need to wrapped in an Output)
|
||||
*/
|
||||
export function makeEd25519Condition(publicKey) {
|
||||
const publicKeyBuffer = new Buffer(base58.decode(publicKey));
|
||||
|
||||
const ed25519Fulfillment = new cc.Ed25519();
|
||||
ed25519Fulfillment.setPublicKey(publicKeyBuffer);
|
||||
const conditionUri = ed25519Fulfillment.getConditionUri();
|
||||
|
||||
return {
|
||||
'details': {
|
||||
'signature': null,
|
||||
'type_id': 4,
|
||||
'type': 'fulfillment',
|
||||
'bitmask': 32,
|
||||
'public_key': publicKey,
|
||||
},
|
||||
'uri': conditionUri,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Output from a Condition.
|
||||
* Note: Assumes the given Condition was generated from a single public key (e.g. a Ed25519 Condition)
|
||||
* @param {object} condition Condition (e.g. a Ed25519 Condition from `makeEd25519Condition()`)
|
||||
* @param {number} amount Amount of the output
|
||||
* @returns {object} An Output usable in a Transaction
|
||||
*/
|
||||
export function makeOutput(condition, amount = 1) {
|
||||
return {
|
||||
amount,
|
||||
condition,
|
||||
'public_keys': [condition.details.public_key],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a `CREATE` transaction holding the `asset`, `metadata`, and `outputs`, to be signed by
|
||||
* the `issuers`.
|
||||
* @param {object} asset Created asset's data
|
||||
* @param {object} metadata Metadata for the Transaction
|
||||
* @param {object[]} outputs Array of Output objects to add to the Transaction.
|
||||
* Think of these as the recipients of the asset after the transaction.
|
||||
* For `CREATE` Transactions, this should usually just be a list of
|
||||
* Outputs wrapping Ed25519 Conditions generated from the issuers' public
|
||||
* keys (so that the issuers are the recipients of the created asset).
|
||||
* @param {...string[]} issuers Public key of one or more issuers to the asset being created by this
|
||||
* Transaction.
|
||||
* Note: Each of the private keys corresponding to the given public
|
||||
* keys MUST be used later (and in the same order) when signing the
|
||||
* Transaction (`signTransaction()`).
|
||||
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
|
||||
* sending it off!
|
||||
*/
|
||||
export function makeCreateTransaction(asset, metadata, outputs, ...issuers) {
|
||||
const assetDefinition = {
|
||||
'data': asset || null,
|
||||
};
|
||||
const inputs = issuers.map((issuer) => makeInputTemplate([issuer]));
|
||||
|
||||
return makeTransaction('CREATE', assetDefinition, metadata, outputs, inputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a `TRANSFER` transaction holding the `asset`, `metadata`, and `outputs`, that fulfills
|
||||
* the `fulfilledOutputs` of `unspentTransaction`.
|
||||
* @param {object} unspentTransaction Previous Transaction you have control over (i.e. can fulfill
|
||||
* its Output Condition)
|
||||
* @param {object} metadata Metadata for the Transaction
|
||||
* @param {object[]} outputs Array of Output objects to add to the Transaction.
|
||||
* Think of these as the recipients of the asset after the transaction.
|
||||
* For `TRANSFER` Transactions, this should usually just be a list of
|
||||
* Outputs wrapping Ed25519 Conditions generated from the public keys of
|
||||
* the recipients.
|
||||
* @param {...number} fulfilledOutputs Indices of the Outputs in `unspentTransaction` that this
|
||||
* Transaction fulfills.
|
||||
* Note that the public keys listed in the fulfilled Outputs
|
||||
* must be used (and in the same order) to sign the Transaction
|
||||
* (`signTransaction()`).
|
||||
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
|
||||
* sending it off!
|
||||
*/
|
||||
export function makeTransferTransaction(unspentTransaction, metadata, outputs, ...fulfilledOutputs) {
|
||||
const inputs = fulfilledOutputs.map((outputIndex) => {
|
||||
const fulfilledOutput = unspentTransaction.outputs[outputIndex];
|
||||
const transactionLink = {
|
||||
'output': outputIndex,
|
||||
'txid': unspentTransaction.id,
|
||||
};
|
||||
|
||||
return makeInputTemplate(fulfilledOutput.public_keys, transactionLink);
|
||||
});
|
||||
|
||||
const assetLink = {
|
||||
'id': unspentTransaction.operation === 'CREATE' ? unspentTransaction.id
|
||||
: unspentTransaction.asset.id
|
||||
};
|
||||
|
||||
return makeTransaction('TRANSFER', assetLink, metadata, outputs, inputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the given `transaction` with the given `privateKey`s, returning a new copy of `transaction`
|
||||
* that's been signed.
|
||||
* Note: Only generates Ed25519 Fulfillments. Thresholds and other types of Fulfillments are left as
|
||||
* an exercise for the user.
|
||||
* @param {object} transaction Transaction to sign. `transaction` is not modified.
|
||||
* @param {...string} privateKeys Private keys associated with the issuers of the `transaction`.
|
||||
* Looped through to iteratively sign any Input Fulfillments found in
|
||||
* the `transaction`.
|
||||
* @returns {object} The signed version of `transaction`.
|
||||
*/
|
||||
export function signTransaction(transaction, ...privateKeys) {
|
||||
const signedTx = clone(transaction);
|
||||
signedTx.inputs.forEach((input, index) => {
|
||||
const privateKey = privateKeys[index];
|
||||
const privateKeyBuffer = new Buffer(base58.decode(privateKey));
|
||||
const serializedTransaction = serializeTransactionIntoCanonicalString(transaction);
|
||||
|
||||
const ed25519Fulfillment = new cc.Ed25519();
|
||||
ed25519Fulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer);
|
||||
const fulfillmentUri = ed25519Fulfillment.serializeUri();
|
||||
|
||||
input.fulfillment = fulfillmentUri;
|
||||
});
|
||||
|
||||
return signedTx;
|
||||
}
|
||||
|
||||
/*********************
|
||||
* Transaction utils *
|
||||
*********************/
|
||||
|
||||
function makeTransactionTemplate() {
|
||||
return {
|
||||
'id': null,
|
||||
'operation': null,
|
||||
'outputs': [],
|
||||
'inputs': [],
|
||||
'metadata': null,
|
||||
'asset': null,
|
||||
'version': '0.9',
|
||||
};
|
||||
}
|
||||
|
||||
function makeInputTemplate(publicKeys = [], fulfills = null, fulfillment = null) {
|
||||
return {
|
||||
fulfillment,
|
||||
fulfills,
|
||||
'owners_before': publicKeys,
|
||||
};
|
||||
}
|
||||
|
||||
function makeTransaction(operation, asset, metadata = null, outputs = [], inputs = []) {
|
||||
const tx = makeTransactionTemplate();
|
||||
tx.operation = operation;
|
||||
tx.asset = asset;
|
||||
tx.metadata = metadata;
|
||||
tx.inputs = inputs;
|
||||
tx.outputs = outputs;
|
||||
|
||||
// Hashing must be done after, as the hash is of the Transaction (up to now)
|
||||
tx.id = hashTransaction(tx);
|
||||
return tx;
|
||||
}
|
||||
|
||||
/****************
|
||||
* Crypto utils *
|
||||
****************/
|
||||
|
||||
function hashTransaction(transaction) {
|
||||
// Safely remove any tx id from the given transaction for hashing
|
||||
const tx = { ...transaction };
|
||||
delete tx.id;
|
||||
|
||||
return sha256Hash(serializeTransactionIntoCanonicalString(tx));
|
||||
}
|
||||
|
||||
function sha256Hash(data) {
|
||||
return sha3.sha3_256
|
||||
.create()
|
||||
.update(data)
|
||||
.hex();
|
||||
}
|
||||
|
||||
function serializeTransactionIntoCanonicalString(transaction) {
|
||||
// BigchainDB signs fulfillments by serializing transactions into a "canonical" format where
|
||||
// each fulfillment URI is removed before sorting the remaining keys
|
||||
const tx = clone(transaction);
|
||||
tx.inputs.forEach((input) => {
|
||||
input.fulfillment = null;
|
||||
});
|
||||
|
||||
// Sort the keys
|
||||
return stableStringify(tx, (a, b) => (a.key > b.key ? 1 : -1));
|
||||
}
|
51
package.json
Normal file
51
package.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "js-bigchaindb-quickstart",
|
||||
"version": "0.0.1",
|
||||
"description": "Some quickstarting for BigchainDB with JavaScript (node + browser)",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sohkai/js-bigchaindb-quickstart.git"
|
||||
},
|
||||
"license": "¯\\_(ツ)_/¯",
|
||||
"author": "BigchainDB",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"lint": "eslint ./",
|
||||
"build": "npm run clean && npm run build:bundle && npm run build:cjs && npm run build:dist",
|
||||
"build:bundle": "webpack",
|
||||
"build:cjs": "cross-env BABEL_ENV=cjs babel index.js -d dist/node",
|
||||
"build:dist": "cross-env NODE_ENV=production webpack -p",
|
||||
"clean": "rimraf dist/bundle dist/node",
|
||||
"test": "echo \"Error: no test specified AWWWW YEAHHH\" && exit 1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.22.2",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "^6.2.10",
|
||||
"babel-plugin-transform-object-assign": "^6.22.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.22.0",
|
||||
"babel-plugin-transform-runtime": "^6.22.0",
|
||||
"babel-preset-latest": "^6.22.0",
|
||||
"babel-runtime": "^6.22.0",
|
||||
"cross-env": "^3.1.4",
|
||||
"eslint": "^3.14.1",
|
||||
"eslint-config-ascribe": "^3.0.1",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"rimraf": "^2.5.4",
|
||||
"webpack": "^2.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bs58": "^4.0.0",
|
||||
"buffer": "^5.0.2",
|
||||
"clone": "^2.1.0",
|
||||
"core-js": "^2.4.1",
|
||||
"five-bells-condition": "=3.3.1",
|
||||
"js-sha3": "^0.5.7",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"tweetnacl": "^0.14.5"
|
||||
},
|
||||
"keywords": [
|
||||
"bigchaindb",
|
||||
"cryptoconditions"
|
||||
]
|
||||
}
|
80
webpack.config.js
Normal file
80
webpack.config.js
Normal file
@ -0,0 +1,80 @@
|
||||
/* eslint-disable strict, no-console, object-shorthand */
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const webpack = require('webpack');
|
||||
|
||||
const PRODUCTION = process.env.NODE_ENV === 'production';
|
||||
|
||||
const PATHS = {
|
||||
ENTRY: path.resolve(__dirname, 'index.js'),
|
||||
BUNDLE: path.resolve(__dirname, 'dist/bundle'),
|
||||
NODE_MODULES: path.resolve(__dirname, 'node_modules'),
|
||||
};
|
||||
|
||||
|
||||
/** PLUGINS **/
|
||||
const PLUGINS = [
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
];
|
||||
|
||||
const PROD_PLUGINS = [
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false,
|
||||
},
|
||||
output: {
|
||||
comments: false,
|
||||
},
|
||||
sourceMap: true,
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
debug: false,
|
||||
minimize: true,
|
||||
}),
|
||||
];
|
||||
|
||||
if (PRODUCTION) {
|
||||
PLUGINS.push(...PROD_PLUGINS);
|
||||
}
|
||||
|
||||
|
||||
/** EXPORTED WEBPACK CONFIG **/
|
||||
const config = {
|
||||
entry: [PATHS.ENTRY],
|
||||
|
||||
output: {
|
||||
filename: PRODUCTION ? 'bundle.min.js' : 'bundle.js',
|
||||
library: 'js-bigchaindb-quickstart',
|
||||
libraryTarget: 'umd',
|
||||
path: PATHS.BUNDLE,
|
||||
},
|
||||
|
||||
devtool: PRODUCTION ? '#source-map' : '#inline-source-map',
|
||||
|
||||
resolve: {
|
||||
extensions: ['.js'],
|
||||
modules: ['node_modules'], // Don't use absolute path here to allow recursive matching
|
||||
},
|
||||
|
||||
plugins: PLUGINS,
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: [PATHS.NODE_MODULES],
|
||||
use: [{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
cacheDirectory: true,
|
||||
},
|
||||
}],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
Loading…
Reference in New Issue
Block a user