Add js tool
This commit is contained in:
commit
6baa42b2f0
|
@ -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']
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
build/*
|
||||
dist/*
|
||||
node_modules/*
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "ascribe"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
*.seed
|
||||
*.log
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
.idea
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
*.sublime-workspace
|
||||
|
||||
.env
|
||||
|
||||
build/*
|
||||
|
||||
node_modules/*
|
|
@ -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) *
|
||||
* *
|
||||
*************************************************************/
|
||||
```
|
|
@ -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.
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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;
|
||||
});
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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