@@ -4,6 +4,9 @@ const base32Encode = require('base32-encode')
4
4
const Big = require ( 'big.js' )
5
5
const NanoDate = require ( 'nano-date' ) . default
6
6
const { Key } = require ( 'interface-datastore' )
7
+ const crypto = require ( 'libp2p-crypto' )
8
+ const PeerId = require ( 'peer-id' )
9
+ const multihash = require ( 'multihashes' )
7
10
8
11
const debug = require ( 'debug' )
9
12
const log = debug ( 'jsipns' )
@@ -13,6 +16,7 @@ const ipnsEntryProto = require('./pb/ipns.proto')
13
16
const { parseRFC3339 } = require ( './utils' )
14
17
const ERRORS = require ( './errors' )
15
18
19
+ const ID_MULTIHASH_CODE = multihash . names . id
16
20
/**
17
21
* Creates a new ipns entry and signs it with the given private key.
18
22
* The ipns entry validity should follow the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision.
@@ -44,7 +48,7 @@ const create = (privateKey, value, seq, lifetime, callback) => {
44
48
45
49
const entry = {
46
50
value : value ,
47
- signature : signature , // TODO confirm format compliance with go-ipfs
51
+ signature : signature ,
48
52
validityType : validityType ,
49
53
validity : isoValidity ,
50
54
sequence : seq
@@ -68,7 +72,7 @@ const validate = (publicKey, entry, callback) => {
68
72
const dataForSignature = ipnsEntryDataForSig ( value , validityType , validity )
69
73
70
74
// Validate Signature
71
- publicKey . verify ( dataForSignature , entry . signature , ( err , result ) => {
75
+ publicKey . verify ( dataForSignature , entry . signature , ( err ) => {
72
76
if ( err ) {
73
77
log . error ( 'record signature verification failed' )
74
78
return callback ( Object . assign ( new Error ( 'record signature verification failed' ) , { code : ERRORS . ERR_SIGNATURE_VERIFICATION } ) )
@@ -100,15 +104,56 @@ const validate = (publicKey, entry, callback) => {
100
104
}
101
105
102
106
/**
103
- * Validates the given ipns entry against the given public key.
107
+ * Embed the given public key in the given entry. While not strictly required,
108
+ * some nodes (eg. DHT servers) may reject IPNS entries that don't embed their
109
+ * public keys as they may not be able to validate them efficiently.
110
+ * As a consequence of nodes needing to validade a record upon receipt, they need
111
+ * the public key associated with it. For olde RSA keys, it is easier if we just
112
+ * send this as part of the record itself. For newer ed25519 keys, the public key
113
+ * can be embedded in the peerId.
104
114
*
105
- * @param {Object } publicKey public key for validating the record .
115
+ * @param {Object } publicKey public key to embed .
106
116
* @param {Object } entry ipns entry record.
107
117
* @param {function(Error) } [callback]
108
118
* @return {Void }
109
119
*/
110
120
const embedPublicKey = ( publicKey , entry , callback ) => {
111
- callback ( new Error ( 'not implemented yet' ) )
121
+ if ( ! publicKey || ! publicKey . bytes || ! entry ) {
122
+ const error = 'one or more of the provided parameters are not defined'
123
+
124
+ log . error ( error )
125
+ return callback ( Object . assign ( new Error ( error ) , { code : ERRORS . ERR_UNDEFINED_PARAMETER } ) )
126
+ }
127
+
128
+ // Create a peer id from the public key.
129
+ PeerId . createFromPubKey ( publicKey . bytes , ( err , peerId ) => {
130
+ if ( err ) {
131
+ log . error ( err )
132
+ return callback ( Object . assign ( new Error ( err ) , { code : ERRORS . ERR_PEER_ID_FROM_PUBLIC_KEY } ) )
133
+ }
134
+
135
+ // Try to extract the public key from the ID. If we can, no need to embed it
136
+ let extractedPublicKey
137
+ try {
138
+ extractedPublicKey = extractPublicKeyFromId ( peerId )
139
+ } catch ( err ) {
140
+ log . error ( err )
141
+ return callback ( Object . assign ( new Error ( err ) , { code : ERRORS . ERR_PUBLIC_KEY_FROM_ID } ) )
142
+ }
143
+
144
+ if ( extractedPublicKey ) {
145
+ return callback ( null , null )
146
+ }
147
+
148
+ // If we failed to extract the public key from the peer ID, embed it in the record.
149
+ try {
150
+ entry . pubKey = crypto . keys . marshalPublicKey ( publicKey )
151
+ } catch ( err ) {
152
+ log . error ( err )
153
+ return callback ( err )
154
+ }
155
+ callback ( null , entry )
156
+ } )
112
157
}
113
158
114
159
/**
@@ -120,7 +165,24 @@ const embedPublicKey = (publicKey, entry, callback) => {
120
165
* @return {Void }
121
166
*/
122
167
const extractPublicKey = ( peerId , entry , callback ) => {
123
- callback ( new Error ( 'not implemented yet' ) )
168
+ if ( ! entry || ! peerId ) {
169
+ const error = 'one or more of the provided parameters are not defined'
170
+
171
+ log . error ( error )
172
+ return callback ( Object . assign ( new Error ( error ) , { code : ERRORS . ERR_UNDEFINED_PARAMETER } ) )
173
+ }
174
+
175
+ if ( entry . pubKey ) {
176
+ let pubKey
177
+ try {
178
+ pubKey = crypto . keys . unmarshalPublicKey ( entry . pubKey )
179
+ } catch ( err ) {
180
+ log . error ( err )
181
+ return callback ( err )
182
+ }
183
+ return callback ( null , pubKey )
184
+ }
185
+ callback ( null , peerId . pubKey )
124
186
}
125
187
126
188
// rawStdEncoding with RFC4648
@@ -139,16 +201,16 @@ const getLocalKey = (key) => new Key(`/ipns/${rawStdEncoding(key)}`)
139
201
* Get key for sharing the record in the routing mechanism.
140
202
* Format: ${base32(/ipns/<HASH>)}, ${base32(/pk/<HASH>)}
141
203
*
142
- * @param {Buffer } key peer identifier object .
204
+ * @param {Buffer } pid peer identifier represented by the multihash of the public key as Buffer .
143
205
* @returns {Object } containing the `nameKey` and the `ipnsKey`.
144
206
*/
145
- const getIdKeys = ( key ) => {
207
+ const getIdKeys = ( pid ) => {
146
208
const pkBuffer = Buffer . from ( '/pk/' )
147
209
const ipnsBuffer = Buffer . from ( '/ipns/' )
148
210
149
211
return {
150
- nameKey : rawStdEncoding ( Buffer . concat ( [ pkBuffer , key ] ) ) ,
151
- ipnsKey : rawStdEncoding ( Buffer . concat ( [ ipnsBuffer , key ] ) )
212
+ pkKey : new Key ( rawStdEncoding ( Buffer . concat ( [ pkBuffer , pid ] ) ) ) ,
213
+ ipnsKey : new Key ( rawStdEncoding ( Buffer . concat ( [ ipnsBuffer , pid ] ) ) )
152
214
}
153
215
}
154
216
@@ -164,13 +226,35 @@ const sign = (privateKey, value, validityType, validity, callback) => {
164
226
} )
165
227
}
166
228
167
- // Create record data for being signed
168
- const ipnsEntryDataForSig = ( value , validityType , eol ) => {
229
+ // Utility for getting the validity type code name of a validity
230
+ const getValidityType = ( validityType ) => {
231
+ if ( validityType . toString ( ) === '0' ) {
232
+ return 'EOL'
233
+ } else {
234
+ const error = `unrecognized validity type ${ validityType . toString ( ) } `
235
+ log . error ( error )
236
+ throw Object . assign ( new Error ( error ) , { code : ERRORS . ERR_UNRECOGNIZED_VALIDITY } )
237
+ }
238
+ }
239
+
240
+ // Utility for creating the record data for being signed
241
+ const ipnsEntryDataForSig = ( value , validityType , validity ) => {
169
242
const valueBuffer = Buffer . from ( value )
170
- const validityTypeBuffer = Buffer . from ( validityType . toString ( ) )
171
- const eolBuffer = Buffer . from ( eol )
243
+ const validityTypeBuffer = Buffer . from ( getValidityType ( validityType ) )
244
+ const validityBuffer = Buffer . from ( validity )
245
+
246
+ return Buffer . concat ( [ valueBuffer , validityBuffer , validityTypeBuffer ] )
247
+ }
248
+
249
+ // Utility for extracting the public key from a peer-id
250
+ const extractPublicKeyFromId = ( peerId ) => {
251
+ const decodedId = multihash . decode ( peerId . id )
252
+
253
+ if ( decodedId . code !== ID_MULTIHASH_CODE ) {
254
+ return null
255
+ }
172
256
173
- return Buffer . concat ( [ valueBuffer , validityTypeBuffer , eolBuffer ] )
257
+ return crypto . keys . unmarshalPublicKey ( decodedId . digest )
174
258
}
175
259
176
260
module . exports = {
0 commit comments