@smartledger/bsv 8.2.0 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -159,17 +159,17 @@ const recovered = bsv.reconstructSecret([shares[0], shares[2], shares[4]]);
159
159
  ### **New Modular Options**
160
160
  ```html
161
161
  <!-- Core compatibility (same size as bsv@1.5.6) -->
162
- <script src="https://unpkg.com/@smartledger/bsv@8.2.0/bsv.min.js"></script>
162
+ <script src="https://unpkg.com/@smartledger/bsv@8.3.0/bsv.min.js"></script>
163
163
 
164
164
  <!-- Add smart contracts when ready -->
165
- <script src="https://unpkg.com/@smartledger/bsv@8.2.0/bsv-smartcontract.min.js"></script>
165
+ <script src="https://unpkg.com/@smartledger/bsv@8.3.0/bsv-smartcontract.min.js"></script>
166
166
 
167
167
  <!-- Add advanced features as needed -->
168
- <script src="https://unpkg.com/@smartledger/bsv@8.2.0/bsv-ltp.min.js"></script>
169
- <script src="https://unpkg.com/@smartledger/bsv@8.2.0/bsv-gdaf.min.js"></script>
168
+ <script src="https://unpkg.com/@smartledger/bsv@8.3.0/bsv-ltp.min.js"></script>
169
+ <script src="https://unpkg.com/@smartledger/bsv@8.3.0/bsv-gdaf.min.js"></script>
170
170
 
171
171
  <!-- Everything in one file -->
172
- <script src="https://unpkg.com/@smartledger/bsv@8.2.0/bsv.bundle.js"></script>
172
+ <script src="https://unpkg.com/@smartledger/bsv@8.3.0/bsv.bundle.js"></script>
173
173
  ```
174
174
 
175
175
  ## 🔍 **Testing Your Migration**
package/index.js CHANGED
@@ -115,6 +115,11 @@ bsv.Block = require('./lib/block')
115
115
  bsv.MerkleBlock = require('./lib/block/merkleblock')
116
116
  bsv.BlockHeader = require('./lib/block/blockheader')
117
117
  bsv.SPV = require('./lib/spv')
118
+
119
+ // BRC-220 NotaryHash. Exposed only now that verification exists: build/parse alone would
120
+ // let a caller construct a record whose proofHash means nothing, with nothing to stop
121
+ // them. See docs/BRC220_PLAN.md.
122
+ bsv.NotaryHash = require('./lib/notaryhash')
118
123
  bsv.HDPrivateKey = require('./lib/hdprivatekey.js')
119
124
  bsv.HDPublicKey = require('./lib/hdpublickey.js')
120
125
  bsv.Networks = require('./lib/networks')
package/index.mjs CHANGED
@@ -23,6 +23,7 @@ export const {
23
23
  Message,
24
24
  Mnemonic,
25
25
  Networks,
26
+ NotaryHash,
26
27
  Opcode,
27
28
  Ordinals,
28
29
  Output,
@@ -7,6 +7,7 @@ var Hash = require('../crypto/hash')
7
7
  var ECDSA = require('../crypto/ecdsa')
8
8
  var Signature = require('../crypto/signature')
9
9
  var $ = require('../util/preconditions')
10
+ var JCS = require('../util/jcs')
10
11
  var _ = require('../util/_')
11
12
 
12
13
  /**
@@ -124,44 +125,7 @@ AttestationSigner._canonicalizeJSON = function(obj) {
124
125
  * @returns {String} RFC 8785 canonical JSON
125
126
  */
126
127
  AttestationSigner._canonicalizeJCS = function (value) {
127
- if (value === null) return 'null'
128
-
129
- var type = typeof value
130
-
131
- if (type === 'boolean') return value ? 'true' : 'false'
132
-
133
- if (type === 'number') {
134
- // JCS has no representation for these; refuse rather than emit `null`, which is what
135
- // JSON.stringify does and which would silently sign a different document.
136
- if (!isFinite(value)) {
137
- throw new Error('Cannot canonicalize non-finite number: ' + value)
138
- }
139
- return JSON.stringify(value)
140
- }
141
-
142
- if (type === 'string') return JSON.stringify(value)
143
-
144
- if (Array.isArray(value)) {
145
- // Arrays are order-significant. `undefined` has no JSON form and becomes null in an
146
- // array, matching JSON.stringify.
147
- return '[' + value.map(function (item) {
148
- return item === undefined ? 'null' : AttestationSigner._canonicalizeJCS(item)
149
- }).join(',') + ']'
150
- }
151
-
152
- if (type === 'object') {
153
- var keys = Object.keys(value).filter(function (key) {
154
- // Absent and explicitly-undefined are indistinguishable in JSON, so both are
155
- // dropped — as JSON.stringify does.
156
- return value[key] !== undefined && typeof value[key] !== 'function'
157
- }).sort()
158
-
159
- return '{' + keys.map(function (key) {
160
- return JSON.stringify(key) + ':' + AttestationSigner._canonicalizeJCS(value[key])
161
- }).join(',') + '}'
162
- }
163
-
164
- throw new Error('Cannot canonicalize value of type: ' + type)
128
+ return JCS.stringify(value)
165
129
  }
166
130
 
167
131
  /**
@@ -0,0 +1,282 @@
1
+ 'use strict'
2
+
3
+ var JCS = require('../util/jcs')
4
+ var Encoding = require('./encoding')
5
+ var NotaryScript = require('./script')
6
+ var $ = require('../util/preconditions')
7
+
8
+ /**
9
+ * BRC-220 certificate — the self-contained object a verifier is handed.
10
+ *
11
+ * Required fields, per the spec: protocol, version, mode, algorithm, hashAlgorithm,
12
+ * payloadHash, publicKey, signature, encoding, proofHash, createdAt, anchor. A batched
13
+ * certificate additionally carries `merkle`.
14
+ *
15
+ * Two things about it are easy to get wrong, and both are load-bearing:
16
+ *
17
+ * - The certificate carries the FULL publicKey and signature in every mode, including
18
+ * hybrid, where only their SHA-256 digests go on chain. That asymmetry is the point of
19
+ * hybrid mode: the chain stays small, the certificate stays complete.
20
+ * - The SPV envelope is ADDITIVE. The spec is explicit that attaching it never changes
21
+ * proofHash and never invalidates a previously issued certificate — which follows from
22
+ * proofHash being computed over the canonical proof bytes, not over the certificate
23
+ * JSON. A test asserts it rather than trusting the reasoning.
24
+ */
25
+
26
+ var Certificate = {}
27
+
28
+ Certificate.PROTOCOL = 'NotaryHash'
29
+ Certificate.VERSION = 1
30
+
31
+ /**
32
+ * The byte representation of publicKey and signature.
33
+ *
34
+ * The spec requires the `encoding` field but never enumerates its values; this library
35
+ * defines them and proposes the definition upstream — see
36
+ * docs/BRC220_ENCODING_AMENDMENT.md.
37
+ *
38
+ * RAW is what everything should emit. For ECDSA-secp256k1 that is 64 bytes of r || s,
39
+ * each a 32-byte big-endian integer, with a 33-byte compressed public key. DER is
40
+ * accepted for Bitcoin-native signers that already hold it, and discouraged: DER is not
41
+ * canonical, so the same signature can encode to 69, 70 or 71 bytes, and those bytes are
42
+ * inside proofHash.
43
+ */
44
+ Certificate.ENCODING = {
45
+ RAW: 'raw',
46
+ DER: 'der'
47
+ }
48
+
49
+ function hex (buf, name) {
50
+ if (!Buffer.isBuffer(buf)) {
51
+ throw new Error(name + ' must be a Buffer of raw bytes, not ' + (typeof buf))
52
+ }
53
+ return buf.toString('hex')
54
+ }
55
+
56
+ /**
57
+ * Build a certificate.
58
+ *
59
+ * `proofHash` is computed here rather than accepted, so a caller cannot supply one that
60
+ * does not match the fields beside it. A certificate whose stated proofHash disagrees
61
+ * with its own contents is exactly the artefact this protocol exists to make impossible,
62
+ * and accepting the value would let one be constructed by mistake.
63
+ *
64
+ * @param {Object} params
65
+ * @param {Number} params.mode - NotaryScript.MODE.*
66
+ * @param {String} params.algorithm
67
+ * @param {String} params.hashAlgorithm - 'SHA-256' for every algorithm the spec lists
68
+ * @param {Buffer} params.payloadHash - raw 32 bytes
69
+ * @param {Buffer} params.publicKey - raw bytes, FULL even in hybrid mode
70
+ * @param {Buffer} params.signature - raw bytes, FULL even in hybrid mode
71
+ * @param {String} [params.encoding='raw']
72
+ * @param {String|Date} [params.createdAt] - defaults to now
73
+ * @param {Object} params.anchor - { txid, blockHeight }
74
+ * @param {Object} [params.merkle] - batch mode: { root, leafIndex, leafCount, path }
75
+ * @returns {Object} certificate
76
+ */
77
+ Certificate.build = function (params) {
78
+ $.checkArgument(params && typeof params === 'object', 'params is required')
79
+ $.checkArgument(params.anchor && typeof params.anchor === 'object', 'anchor is required')
80
+ $.checkArgument(typeof params.anchor.txid === 'string', 'anchor.txid must be a string')
81
+
82
+ var encoding = params.encoding || Certificate.ENCODING.RAW
83
+ $.checkArgument(encoding === Certificate.ENCODING.RAW || encoding === Certificate.ENCODING.DER,
84
+ 'encoding must be "raw" or "der"')
85
+
86
+ var createdAt = params.createdAt
87
+ ? (params.createdAt instanceof Date ? params.createdAt.toISOString() : params.createdAt)
88
+ : new Date().toISOString()
89
+
90
+ var createdAtUnix = Encoding.toUnixSeconds(createdAt)
91
+
92
+ var proofHash = Encoding.proofHash({
93
+ algorithm: params.algorithm,
94
+ hashAlgorithm: params.hashAlgorithm,
95
+ payloadHash: params.payloadHash,
96
+ publicKey: params.publicKey,
97
+ signature: params.signature,
98
+ createdAtUnix: createdAtUnix
99
+ })
100
+
101
+ var certificate = {
102
+ protocol: Certificate.PROTOCOL,
103
+ version: Certificate.VERSION,
104
+ mode: params.mode,
105
+ algorithm: params.algorithm,
106
+ hashAlgorithm: params.hashAlgorithm,
107
+ payloadHash: hex(params.payloadHash, 'payloadHash'),
108
+ publicKey: hex(params.publicKey, 'publicKey'),
109
+ signature: hex(params.signature, 'signature'),
110
+ encoding: encoding,
111
+ proofHash: proofHash.toString('hex'),
112
+ createdAt: createdAt,
113
+ anchor: {
114
+ txid: params.anchor.txid,
115
+ blockHeight: params.anchor.blockHeight
116
+ }
117
+ }
118
+
119
+ if (params.mode === NotaryScript.MODE.BATCH) {
120
+ $.checkArgument(params.merkle && typeof params.merkle === 'object',
121
+ 'batch certificates require a merkle inclusion proof')
122
+ certificate.merkle = {
123
+ root: params.merkle.root,
124
+ leafIndex: params.merkle.leafIndex,
125
+ leafCount: params.merkle.leafCount,
126
+ path: params.merkle.path
127
+ }
128
+ }
129
+
130
+ return certificate
131
+ }
132
+
133
+ /**
134
+ * Recompute the proofHash a certificate's own fields imply.
135
+ *
136
+ * This is validity check 2 of the three the spec requires, and it needs no network. It
137
+ * decodes the hex fields back to bytes first: the canonical proof bytes are over the raw
138
+ * bytes, and hex is twice as long, so hashing the strings would produce a value that is
139
+ * wrong in a way that still looks like a hash.
140
+ *
141
+ * @param {Object} certificate
142
+ * @returns {Buffer} 32 bytes
143
+ */
144
+ Certificate.recomputeProofHash = function (certificate) {
145
+ $.checkArgument(certificate && typeof certificate === 'object', 'certificate is required')
146
+ return Encoding.proofHash({
147
+ algorithm: certificate.algorithm,
148
+ hashAlgorithm: certificate.hashAlgorithm,
149
+ payloadHash: Buffer.from(certificate.payloadHash, 'hex'),
150
+ publicKey: Buffer.from(certificate.publicKey, 'hex'),
151
+ signature: Buffer.from(certificate.signature, 'hex'),
152
+ createdAtUnix: Encoding.toUnixSeconds(certificate.createdAt)
153
+ })
154
+ }
155
+
156
+ /**
157
+ * Does the stated proofHash match the fields beside it?
158
+ *
159
+ * Strict boolean. Returns false rather than throwing on a malformed certificate, because
160
+ * "this certificate is not valid" is the honest answer to a certificate that cannot be
161
+ * parsed, and a caller writing `if (proofHashMatches(c))` must not get a truthy object.
162
+ *
163
+ * @param {Object} certificate
164
+ * @returns {Boolean}
165
+ */
166
+ Certificate.proofHashMatches = function (certificate) {
167
+ try {
168
+ if (!certificate || typeof certificate.proofHash !== 'string') return false
169
+ var stated = Buffer.from(certificate.proofHash, 'hex')
170
+ if (stated.length !== 32) return false
171
+ return Certificate.recomputeProofHash(certificate).equals(stated)
172
+ } catch (e) {
173
+ return false
174
+ }
175
+ }
176
+
177
+ /** Every field the spec requires of a certificate, in the order it lists them. */
178
+ Certificate.REQUIRED_FIELDS = [
179
+ 'protocol', 'version', 'mode', 'algorithm', 'hashAlgorithm', 'payloadHash',
180
+ 'publicKey', 'signature', 'encoding', 'proofHash', 'createdAt', 'anchor'
181
+ ]
182
+
183
+ /**
184
+ * Check a certificate's SHAPE — that the required fields are present and well-formed.
185
+ *
186
+ * This is NOT verification. It says nothing about whether the signature is valid, whether
187
+ * the proofHash matches, or whether the anchor exists. It exists so that those checks can
188
+ * assume a parseable object, and it returns a list of problems rather than a boolean so
189
+ * the caller can report which field is wrong.
190
+ *
191
+ * @param {Object} certificate
192
+ * @returns {Array<String>} problems; empty means the shape is fine
193
+ */
194
+ Certificate.validateShape = function (certificate) {
195
+ var problems = []
196
+
197
+ if (!certificate || typeof certificate !== 'object') {
198
+ return ['certificate must be an object']
199
+ }
200
+
201
+ Certificate.REQUIRED_FIELDS.forEach(function (field) {
202
+ if (certificate[field] === undefined) problems.push('missing required field: ' + field)
203
+ })
204
+
205
+ if (certificate.protocol !== undefined && certificate.protocol !== Certificate.PROTOCOL) {
206
+ problems.push('protocol must be "' + Certificate.PROTOCOL + '"')
207
+ }
208
+ if (certificate.version !== undefined && certificate.version !== Certificate.VERSION) {
209
+ problems.push('unsupported version: ' + certificate.version)
210
+ }
211
+ ;[[certificate.payloadHash, 'payloadHash', 32], [certificate.proofHash, 'proofHash', 32]]
212
+ .forEach(function (pair) {
213
+ if (pair[0] === undefined) return
214
+ if (typeof pair[0] !== 'string' || !/^[0-9a-fA-F]*$/.test(pair[0])) {
215
+ problems.push(pair[1] + ' must be a hex string')
216
+ } else if (pair[0].length !== pair[2] * 2) {
217
+ problems.push(pair[1] + ' must be ' + pair[2] + ' bytes (' + pair[2] * 2 + ' hex chars)')
218
+ }
219
+ })
220
+
221
+ if (certificate.mode === NotaryScript.MODE.BATCH && certificate.merkle === undefined) {
222
+ problems.push('batch certificates require a merkle inclusion proof')
223
+ }
224
+
225
+ return problems
226
+ }
227
+
228
+ /**
229
+ * Attach the SPV envelope to a finished certificate.
230
+ *
231
+ * Returns a NEW object rather than mutating: a certificate that has been handed to
232
+ * someone should not change under them, and a caller comparing before and after needs
233
+ * both.
234
+ *
235
+ * The spec guarantees this never changes proofHash, because proofHash is over the
236
+ * canonical proof bytes and the envelope is not among them. `attachSPV` asserts that
237
+ * rather than assuming it — if the guarantee ever broke, every previously issued
238
+ * certificate would become unverifiable, and it should break loudly here rather than
239
+ * quietly at a verifier.
240
+ *
241
+ * @param {Object} certificate
242
+ * @param {Object} spv - { rawTx, blockHash, blockHeight, merkleProof, format }
243
+ * @returns {Object} a new certificate with `spv` attached
244
+ */
245
+ Certificate.attachSPV = function (certificate, spv) {
246
+ $.checkArgument(certificate && typeof certificate === 'object', 'certificate is required')
247
+ $.checkArgument(spv && typeof spv === 'object', 'spv envelope is required')
248
+
249
+ var before = certificate.proofHash
250
+
251
+ var withSPV = Object.assign({}, certificate, {
252
+ spv: {
253
+ rawTx: spv.rawTx,
254
+ blockHash: spv.blockHash,
255
+ blockHeight: spv.blockHeight,
256
+ merkleProof: spv.merkleProof,
257
+ format: spv.format || 'TSC'
258
+ }
259
+ })
260
+
261
+ if (withSPV.proofHash !== before) {
262
+ throw new Error('attaching the SPV envelope changed proofHash; this must never happen')
263
+ }
264
+
265
+ return withSPV
266
+ }
267
+
268
+ /**
269
+ * Canonical JSON for the certificate, per RFC 8785.
270
+ *
271
+ * Used for transport and for hashing the certificate itself. Note this is NOT what
272
+ * proofHash is computed over — that is the length-prefixed binary of the proof fields.
273
+ * Confusing the two produces a value that looks like a proofHash and is not one.
274
+ *
275
+ * @param {Object} certificate
276
+ * @returns {String}
277
+ */
278
+ Certificate.canonicalize = function (certificate) {
279
+ return JCS.stringify(certificate)
280
+ }
281
+
282
+ module.exports = Certificate
@@ -0,0 +1,150 @@
1
+ 'use strict'
2
+
3
+ var Hash = require('../crypto/hash')
4
+ var $ = require('../util/preconditions')
5
+
6
+ /**
7
+ * BRC-220 (NotaryHash) canonical binary encoding.
8
+ *
9
+ * The whole point of this file is that two independent implementations produce the same
10
+ * bytes. Everything here is fixed by the spec; nothing is a local convention.
11
+ *
12
+ * See docs/BRC220_PLAN.md for how this fits together.
13
+ */
14
+
15
+ var Encoding = {}
16
+
17
+ /** The protocol prefix that opens the canonical bytes. Verbatim from the spec. */
18
+ Encoding.PROTOCOL_PREFIX = 'NotaryHash/1.0'
19
+
20
+ /** Protocol version carried as a single byte inside the canonical bytes. */
21
+ Encoding.VERSION = 1
22
+
23
+ /**
24
+ * Length-prefix a value: `lp(x) = u32be(len(x)) || x`.
25
+ *
26
+ * The length is the BYTE length, which is why strings are UTF-8 encoded first rather than
27
+ * measured with `.length` — those differ the moment a non-ASCII character appears, and
28
+ * the resulting proof would be unverifiable by anyone else.
29
+ *
30
+ * @param {Buffer|String} value - bytes, or text to encode as UTF-8
31
+ * @returns {Buffer}
32
+ */
33
+ Encoding.lp = function (value) {
34
+ var buf = Buffer.isBuffer(value) ? value : Buffer.from(String(value), 'utf8')
35
+ var prefix = Buffer.alloc(4)
36
+ prefix.writeUInt32BE(buf.length, 0)
37
+ return Buffer.concat([prefix, buf])
38
+ }
39
+
40
+ /**
41
+ * Require a byte field to actually be bytes.
42
+ *
43
+ * Certificates carry `payloadHash`, `publicKey` and `signature` as HEX STRINGS, and the
44
+ * canonical bytes are over the RAW BYTES those strings decode to. Passing the hex string
45
+ * straight through would length-prefix 64 bytes where the spec wants 32 — a proofHash
46
+ * that is self-consistent, verifies against itself, and matches no other implementation.
47
+ * That is the failure this guard exists to make impossible, so hex must be decoded by the
48
+ * caller rather than guessed at here.
49
+ *
50
+ * @private
51
+ */
52
+ function requireBytes (value, name) {
53
+ if (!Buffer.isBuffer(value)) {
54
+ throw new Error(
55
+ name + ' must be a Buffer of raw bytes, not ' + (typeof value) +
56
+ '. Certificates store this field as hex; decode it with Buffer.from(hex, \'hex\') ' +
57
+ 'before hashing, or the length prefix covers the wrong number of bytes.'
58
+ )
59
+ }
60
+ return value
61
+ }
62
+
63
+ /**
64
+ * Encode a Unix timestamp as `u64be`.
65
+ *
66
+ * Seconds, not milliseconds. `Date.now()` is milliseconds, so a caller who forgets to
67
+ * divide produces a timestamp ~1000x in the future that still encodes cleanly — a test
68
+ * pins the boundary rather than trusting the caller.
69
+ *
70
+ * @param {Number} seconds - whole seconds since the Unix epoch
71
+ * @returns {Buffer} 8 bytes, big-endian
72
+ */
73
+ Encoding.u64be = function (seconds) {
74
+ $.checkArgument(typeof seconds === 'number' && isFinite(seconds), 'createdAtUnix must be a finite number')
75
+ $.checkArgument(Number.isInteger(seconds), 'createdAtUnix must be whole seconds, not milliseconds or a fraction')
76
+ $.checkArgument(seconds >= 0, 'createdAtUnix must not be negative')
77
+ var buf = Buffer.alloc(8)
78
+ buf.writeBigUInt64BE(BigInt(seconds), 0)
79
+ return buf
80
+ }
81
+
82
+ /**
83
+ * Convert an ISO 8601 timestamp to the whole seconds the canonical bytes carry.
84
+ *
85
+ * Truncates rather than rounds: a certificate's `createdAt` has millisecond precision and
86
+ * `createdAtUnix` does not, so two implementations must agree on which way the fractional
87
+ * part goes. Truncation is the only choice that never moves a timestamp forward.
88
+ *
89
+ * @param {String|Date} createdAt
90
+ * @returns {Number} whole seconds
91
+ */
92
+ Encoding.toUnixSeconds = function (createdAt) {
93
+ var date = createdAt instanceof Date ? createdAt : new Date(createdAt)
94
+ $.checkArgument(!isNaN(date.getTime()), 'createdAt is not a valid date: ' + createdAt)
95
+ return Math.floor(date.getTime() / 1000)
96
+ }
97
+
98
+ /**
99
+ * Build the canonical proof bytes.
100
+ *
101
+ * lp("NotaryHash/1.0") || u8(version=1) ||
102
+ * lp(algorithm) || lp(hashAlgorithm) ||
103
+ * lp(payloadHash) || lp(publicKey) || lp(signature) ||
104
+ * u64be(createdAtUnix)
105
+ *
106
+ * The protocol prefix, the version byte and the timestamp are part of this and are NEVER
107
+ * part of the on-chain record. So the OP_RETURN output alone cannot reconstruct the
108
+ * proofHash — a verifier needs the certificate too. That looks like an omission when
109
+ * reading the on-chain format in isolation; it is deliberate, and it is what lets an SPV
110
+ * envelope be added afterwards without disturbing the hash.
111
+ *
112
+ * @param {Object} fields
113
+ * @param {String} fields.algorithm - e.g. 'ECDSA-secp256k1', 'ML-DSA-65'
114
+ * @param {String} fields.hashAlgorithm - e.g. 'SHA-256'
115
+ * @param {Buffer} fields.payloadHash - raw bytes
116
+ * @param {Buffer} fields.publicKey - raw bytes
117
+ * @param {Buffer} fields.signature - raw bytes
118
+ * @param {Number} fields.createdAtUnix - whole seconds
119
+ * @returns {Buffer}
120
+ */
121
+ Encoding.canonicalBytes = function (fields) {
122
+ $.checkArgument(fields && typeof fields === 'object', 'fields object is required')
123
+ $.checkArgument(typeof fields.algorithm === 'string' && fields.algorithm.length > 0,
124
+ 'algorithm must be a non-empty string')
125
+ $.checkArgument(typeof fields.hashAlgorithm === 'string' && fields.hashAlgorithm.length > 0,
126
+ 'hashAlgorithm must be a non-empty string')
127
+
128
+ return Buffer.concat([
129
+ Encoding.lp(Encoding.PROTOCOL_PREFIX),
130
+ Buffer.from([Encoding.VERSION]),
131
+ Encoding.lp(fields.algorithm),
132
+ Encoding.lp(fields.hashAlgorithm),
133
+ Encoding.lp(requireBytes(fields.payloadHash, 'payloadHash')),
134
+ Encoding.lp(requireBytes(fields.publicKey, 'publicKey')),
135
+ Encoding.lp(requireBytes(fields.signature, 'signature')),
136
+ Encoding.u64be(fields.createdAtUnix)
137
+ ])
138
+ }
139
+
140
+ /**
141
+ * The integrity root: `proofHash = SHA-256(canonicalBytes)`.
142
+ *
143
+ * @param {Object} fields - as for canonicalBytes()
144
+ * @returns {Buffer} 32 bytes
145
+ */
146
+ Encoding.proofHash = function (fields) {
147
+ return Hash.sha256(Encoding.canonicalBytes(fields))
148
+ }
149
+
150
+ module.exports = Encoding