Skip to content

Commit dd2b044

Browse files
committed
feature(quickstart): Add (currently broken) initial quickstart helpers
1 parent 89368e8 commit dd2b044

File tree

1 file changed

+226
-0
lines changed

1 file changed

+226
-0
lines changed

index.js

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import { Buffer } from 'buffer';
2+
import crypto from 'crypto';
3+
4+
import base58 from 'bs58';
5+
import clone from 'clone';
6+
import cc from 'five-bells-condition';
7+
import nacl from 'tweetnacl';
8+
import stableStringify from 'json-stable-stringify';
9+
import uuid from 'uuid';
10+
11+
/**
12+
* @class Keypair Ed25519 keypair in base58 (as BigchainDB expects base58 keys)
13+
* @type {Object}
14+
* @property {string} publicKey
15+
* @property {string} privateKey
16+
*/
17+
export function Keypair() {
18+
const keyPair = nacl.sign.keyPair();
19+
this.publicKey = base58.encode(keyPair.publicKey);
20+
this.privateKey = base58.encode(keyPair.secretKey);
21+
}
22+
23+
/**
24+
* Create an Ed25519 Cryptocondition from an Ed25519 public key to put into a transaction
25+
* @param {string} publicKey base58 encoded Ed25519 public key for the new "owner"
26+
* @returns {object} Ed25519 Condition in a format compatible with BigchainDB
27+
* Note: Assumes that 'cid' will be adjusted afterwards.
28+
*/
29+
export function makeEd25519Condition(publicKey) {
30+
const publicKeyBuffer = new Buffer(base58.decode(publicKey));
31+
32+
const ed25519Fulfillment = new cc.Ed25519();
33+
ed25519Fulfillment.setPublicKey(publicKeyBuffer);
34+
const conditionUri = ed25519Fulfillment.getConditionUri();
35+
36+
return {
37+
'amount': 1,
38+
'condition': {
39+
'cid': 0, // Will be adjusted after adding the condition to the transaction
40+
'owners_after': [publicKey],
41+
'uri': conditionUri,
42+
'details': {
43+
'signature': null,
44+
'type_id': 4,
45+
'type': 'fulfillment',
46+
'bitmask': 32,
47+
'public_key': publicKey,
48+
},
49+
},
50+
};
51+
}
52+
53+
/**
54+
* Create an "empty" Ed25519 fulfillment from a ED25519 public key to put into a transaction.
55+
* This "mock" step is necessary in order for a transaction to be completely out so it can later
56+
* be serialized and signed.
57+
* @param {string} publicKey base58 encoded Ed25519 public key for the previous "owner"
58+
* @returns {object} Ed25519 Condition in a format compatible with BigchainDB
59+
* Note: Assumes that 'cid' will be adjusted afterwards.
60+
*/
61+
export function makeEd25519Fulfillment(publicKey) {
62+
return {
63+
'owners_before': [publicKey],
64+
'fid': 0, // Will be adjusted after adding the fulfillment to the transaction
65+
'input': null, // Will be filled out after adding the fulfillment to the transaction
66+
'fulfillment': null, // Will be generated during signing
67+
};
68+
}
69+
70+
/**
71+
* Generate a `CREATE` transaction holding the `assetData`, `metaData`, `conditions`, and
72+
* `fulfillments`.
73+
* @param {object} assetData Asset's `data` property
74+
* @param {object=} metaData Metadata's `data` property
75+
* @param {object[]=} conditions Array of condition objectss to add to the transaction.
76+
* Think of these as the new "owners" of the asset after the transaction.
77+
* For `CREATE` transactions, this should usually just be an Ed25519
78+
* Condition generated from the creator's public key.
79+
* @param {object[]=} fulfillments Array of fulfillment objects to add to the transaction
80+
* Think of these as proofs that you can manipulate the asset.
81+
* For `CREATE` transactions, this should usually just be an
82+
* Ed25519 Fulfillment generated from the creator's public key.
83+
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
84+
* sending it off!
85+
*/
86+
export function makeCreateTransaction(assetData, metadata, conditions, fulfillments) {
87+
const asset = {
88+
'id': uuid.v4(),
89+
'data': assetData || null,
90+
'divisible': false,
91+
'updatable': false,
92+
'refillable': false,
93+
};
94+
95+
return makeTransaction('CREATE', asset, metadata, conditions, fulfillments);
96+
}
97+
98+
/**
99+
* Generate a `TRANSFER` transaction holding the `assetData`, `metaData`, `conditions`, and
100+
* `fulfillments`.
101+
* @param {object} unspentTransaction Transaction you have control over (i.e. can fulfill its
102+
* Condition).
103+
* @param {object=} metaData Metadata's `data` property
104+
* @param {object[]=} conditions Array of condition objects to add to the transaction
105+
* Think of these as the new "owners" of the asset after the transaction.
106+
* For `TRANSFER` transactions, this should usually just be an
107+
* Ed25519 Condition generated from the new owner's public key.
108+
* @param {object[]=} fulfillments Array of fulfillment objects to add to the transaction
109+
* Think of these as proofs that you can manipulate the asset.
110+
* For `TRANSFER` transactions, this should usually just be an
111+
* Ed25519 Fulfillment generated from the creator's public key.
112+
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
113+
* sending it off!
114+
*/
115+
export function makeTransferTransaction(unspentTransaction, metadata, conditions, fulfillments) {
116+
// Add transactionLinks to link fulfillments with previous transaction's conditions
117+
// NOTE: Naively assumes that fulfillments are given in the same order as the conditions they're
118+
// meant to fulfill
119+
fulfillments.forEach((fulfillment, index) => {
120+
fulfillment.input = {
121+
'cid': index,
122+
'txid': unspentTransaction.id,
123+
};
124+
});
125+
126+
const assetLink = { 'id': unspentTransaction.transaction.asset.id };
127+
128+
return makeTransaction('TRANSFER', assetLink, metadata, conditions, fulfillments);
129+
}
130+
131+
/**
132+
* Sign a transaction with the given `privateKey`s.
133+
* @param {object} transaction Transaction to sign
134+
* @param {...string} privateKeys base58 Ed25519 private keys.
135+
* Looped through once to iteratively sign any Fulfillments found in
136+
* the `transaction`.
137+
* @returns {object} The original transaction, signed in-place.
138+
*/
139+
export function signTransaction(transaction, ...privateKeys) {
140+
transaction.transaction.fulfillments.forEach((fulfillment, index) => {
141+
const privateKey = privateKeys[index];
142+
const privateKeyBuffer = new Buffer(base58.decode(privateKey));
143+
const seriailizedTransaction = serializeTransactionWithoutFulfillments(transaction);
144+
145+
const ed25519Fulfillment = new cc.Ed25519();
146+
ed25519Fulfillment.sign(new Buffer(seriailizedTransaction), privateKeyBuffer);
147+
const fulfillmentUri = ed25519Fulfillment.serializeUri();
148+
149+
fulfillment.fulfillment = fulfillmentUri;
150+
});
151+
152+
return transaction;
153+
}
154+
155+
/*********************
156+
* Transaction utils *
157+
*********************/
158+
159+
function makeTransactionTemplate() {
160+
return {
161+
'id': null,
162+
'version': 1,
163+
'transaction': {
164+
'operation': null,
165+
'conditions': [],
166+
'fulfillments': [],
167+
'metadata': null,
168+
'asset': null,
169+
},
170+
};
171+
}
172+
173+
function makeTransaction(operation, asset, metadata, conditions = [], fulfillments = []) {
174+
const tx = makeTransactionTemplate();
175+
tx.operation = operation;
176+
tx.transaction.asset = asset;
177+
178+
if (metadata) {
179+
tx.transaction.metadata = {
180+
'id': uuid.v4(),
181+
'data': metadata,
182+
};
183+
}
184+
185+
tx.transaction.conditions.push(...conditions);
186+
tx.transaction.conditions.forEach((condition, index) => {
187+
condition.cid = index;
188+
});
189+
190+
tx.transaction.fulfillments.push(...fulfillments);
191+
tx.transaction.fulfillments.forEach((fulfillment, index) => {
192+
fulfillment.fid = index;
193+
});
194+
195+
tx.id = hashTransaction(tx);
196+
return tx;
197+
}
198+
199+
/****************
200+
* Crypto utils *
201+
****************/
202+
203+
function hashTransaction(transaction) {
204+
return sha256Hash(serializeTransactionWithoutFulfillments(transaction));
205+
}
206+
207+
function sha256Hash(data) {
208+
return crypto
209+
.createHash('sha256')
210+
.update(data)
211+
.digest('hex');
212+
}
213+
214+
function serializeTransactionWithoutFulfillments(transaction) {
215+
// BigchainDB creates transactions IDs and signs fulfillments by serializing transactions
216+
// into a "canonical" format where the transaction id and each fulfillment URI are ignored and
217+
// the remaining keys are sorted
218+
const tx = clone(transaction);
219+
delete tx.id;
220+
tx.transaction.fulfillments.forEach((fulfillment) => {
221+
fulfillment.fulfillment = null;
222+
});
223+
224+
// Sort the keys
225+
return stableStringify(tx, (a, b) => (a.key > b.key ? 1 : -1));
226+
}

0 commit comments

Comments
 (0)