@@ -6,15 +6,14 @@ import cc from 'five-bells-condition';
6
6
import nacl from 'tweetnacl' ;
7
7
import sha3 from 'js-sha3' ;
8
8
import stableStringify from 'json-stable-stringify' ;
9
- import uuid from 'uuid' ;
10
9
11
10
/**
12
11
* @class Keypair Ed25519 keypair in base58 (as BigchainDB expects base58 keys)
13
12
* @type {Object }
14
13
* @property {string } publicKey
15
14
* @property {string } privateKey
16
15
*/
17
- export function Keypair ( ) {
16
+ export function Ed25519Keypair ( ) {
18
17
const keyPair = nacl . sign . keyPair ( ) ;
19
18
this . publicKey = base58 . encode ( keyPair . publicKey ) ;
20
19
@@ -23,10 +22,9 @@ export function Keypair() {
23
22
}
24
23
25
24
/**
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)
30
28
*/
31
29
export function makeEd25519Condition ( publicKey ) {
32
30
const publicKeyBuffer = new Buffer ( base58 . decode ( publicKey ) ) ;
@@ -36,122 +34,123 @@ export function makeEd25519Condition(publicKey) {
36
34
const conditionUri = ed25519Fulfillment . getConditionUri ( ) ;
37
35
38
36
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 ,
51
43
} ,
44
+ 'uri' : conditionUri ,
52
45
} ;
53
46
}
54
47
55
48
/**
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
62
54
*/
63
- export function makeEd25519Fulfillment ( publicKey ) {
55
+ export function makeOutput ( condition , amount = 1 ) {
64
56
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 ] ,
69
60
} ;
70
61
}
71
62
72
63
/**
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()`).
85
78
* @returns {object } Unsigned transaction -- make sure to call signTransaction() on it before
86
79
* sending it off!
87
80
*/
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 ,
95
84
} ;
85
+ const inputs = issuers . map ( ( issuer ) => makeInputTemplate ( [ issuer ] ) ) ;
96
86
97
- return makeTransaction ( 'CREATE' , asset , metadata , conditions , fulfillments ) ;
87
+ return makeTransaction ( 'CREATE' , assetDefinition , metadata , outputs , inputs ) ;
98
88
}
99
89
100
90
/**
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()`).
114
106
* @returns {object } Unsigned transaction -- make sure to call signTransaction() on it before
115
107
* sending it off!
116
108
*/
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 ,
124
114
'txid' : unspentTransaction . id ,
125
115
} ;
116
+
117
+ return makeInputTemplate ( fulfilledOutput . public_keys , transactionLink ) ;
126
118
} ) ;
127
119
128
- const assetLink = { 'id' : unspentTransaction . transaction . asset . id } ;
120
+ const assetLink = {
121
+ 'id' : unspentTransaction . operation === 'CREATE' ? unspentTransaction . id
122
+ : unspentTransaction . asset . id
123
+ } ;
129
124
130
- return makeTransaction ( 'TRANSFER' , assetLink , metadata , conditions , fulfillments ) ;
125
+ return makeTransaction ( 'TRANSFER' , assetLink , metadata , outputs , inputs ) ;
131
126
}
132
127
133
128
/**
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
138
136
* the `transaction`.
139
- * @returns {object } The original transaction, signed in-place .
137
+ * @returns {object } The signed version of `transaction` .
140
138
*/
141
139
export function signTransaction ( transaction , ...privateKeys ) {
142
- transaction . transaction . fulfillments . forEach ( ( fulfillment , index ) => {
140
+ const signedTx = clone ( transaction ) ;
141
+ signedTx . inputs . forEach ( ( input , index ) => {
143
142
const privateKey = privateKeys [ index ] ;
144
143
const privateKeyBuffer = new Buffer ( base58 . decode ( privateKey ) ) ;
145
- const seriailizedTransaction = serializeTransactionWithoutFulfillments ( transaction ) ;
144
+ const serializedTransaction = serializeTransactionIntoCanonicalString ( transaction ) ;
146
145
147
146
const ed25519Fulfillment = new cc . Ed25519 ( ) ;
148
- ed25519Fulfillment . sign ( new Buffer ( seriailizedTransaction ) , privateKeyBuffer ) ;
147
+ ed25519Fulfillment . sign ( new Buffer ( serializedTransaction ) , privateKeyBuffer ) ;
149
148
const fulfillmentUri = ed25519Fulfillment . serializeUri ( ) ;
150
149
151
- fulfillment . fulfillment = fulfillmentUri ;
150
+ input . fulfillment = fulfillmentUri ;
152
151
} ) ;
153
152
154
- return transaction ;
153
+ return signedTx ;
155
154
}
156
155
157
156
/*********************
@@ -161,39 +160,32 @@ export function signTransaction(transaction, ...privateKeys) {
161
160
function makeTransactionTemplate ( ) {
162
161
return {
163
162
'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 ,
172
177
} ;
173
178
}
174
179
175
- function makeTransaction ( operation , asset , metadata , conditions = [ ] , fulfillments = [ ] ) {
180
+ function makeTransaction ( operation , asset , metadata = null , outputs = [ ] , inputs = [ ] ) {
176
181
const tx = makeTransactionTemplate ( ) ;
177
182
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 ;
196
187
188
+ // Hashing must be done after, as the hash is of the Transaction (up to now)
197
189
tx . id = hashTransaction ( tx ) ;
198
190
return tx ;
199
191
}
@@ -203,7 +195,11 @@ function makeTransaction(operation, asset, metadata, conditions = [], fulfillmen
203
195
****************/
204
196
205
197
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 ) ) ;
207
203
}
208
204
209
205
function sha256Hash ( data ) {
@@ -213,14 +209,12 @@ function sha256Hash(data) {
213
209
. hex ( ) ;
214
210
}
215
211
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
220
215
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 ;
224
218
} ) ;
225
219
226
220
// Sort the keys
0 commit comments