Skip to content

Commit a79fd55

Browse files
bind v1 sigs to manifest (#479)
* bind v1 sigs to manifest * verifier sign API checks for correct signer * update some of the manifest tests, more to do still * update scheme and fix tests * fix arg pos * pass down manifest hash * never a need to pass compat option * revert batch test upgrade * raw adds no benefit here --------- Co-authored-by: Christophe Diederichs <[email protected]>
1 parent 3137241 commit a79fd55

File tree

5 files changed

+136
-92
lines changed

5 files changed

+136
-92
lines changed

lib/caps.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ exports.replicate = function (isInitiator, key, handshakeHash) {
2525
return out
2626
}
2727

28-
exports.treeSignable = function (namespace, hash, length, fork) {
28+
exports.treeSignable = function (manifestHash, treeHash, length, fork) {
2929
const state = { start: 0, end: 112, buffer: b4a.allocUnsafe(112) }
30-
c.raw.encode(state, TREE)
31-
c.raw.encode(state, namespace)
32-
c.raw.encode(state, hash)
30+
c.fixed32.encode(state, TREE)
31+
c.fixed32.encode(state, manifestHash)
32+
c.fixed32.encode(state, treeHash)
3333
c.uint64.encode(state, length)
3434
c.uint64.encode(state, fork)
3535
return state.buffer
@@ -38,8 +38,8 @@ exports.treeSignable = function (namespace, hash, length, fork) {
3838
exports.treeSignableCompat = function (hash, length, fork, noHeader) {
3939
const end = noHeader ? 48 : 80
4040
const state = { start: 0, end, buffer: b4a.allocUnsafe(end) }
41-
if (!noHeader) c.raw.encode(state, TREE) // ultra legacy mode, kill in future major
42-
c.raw.encode(state, hash)
41+
if (!noHeader) c.fixed32.encode(state, TREE) // ultra legacy mode, kill in future major
42+
c.fixed32.encode(state, hash)
4343
c.uint64.encode(state, length)
4444
c.uint64.encode(state, fork)
4545
return state.buffer

lib/core.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ module.exports = class Core {
158158
while (bitfield.get(header.hints.contiguousLength)) header.hints.contiguousLength++
159159
}
160160

161-
const verifier = header.manifest ? new Verifier(header.manifest, { compat: Verifier.isCompat(header.key, header.manifest), crypto, legacy }) : null
161+
const verifier = header.manifest ? new Verifier(header.key, header.manifest, { crypto, legacy }) : null
162162

163163
for (const e of entries) {
164164
if (e.userData) {
@@ -197,13 +197,12 @@ module.exports = class Core {
197197
if (!manifest && b4a.equals(keyPair.publicKey, this.header.key)) manifest = Verifier.defaultSignerManifest(this.header.key)
198198
if (!manifest) return
199199

200-
const compat = Verifier.isCompat(this.header.key, manifest)
201-
const verifier = new Verifier(manifest, { compat, crypto: this.crypto, legacy: this._legacy })
200+
const verifier = new Verifier(this.header.key, manifest, { crypto: this.crypto, legacy: this._legacy })
202201

203202
if (verifier.prologue) this.tree.setPrologue(verifier.prologue)
204203

205-
this.compat = compat
206204
this.header.manifest = manifest
205+
this.compat = verifier.compat
207206
this.verifier = verifier
208207
this._manifestFlushed = false
209208

@@ -681,15 +680,15 @@ module.exports = class Core {
681680
}
682681
}
683682

684-
const verifier = this.verifier || new Verifier(manifest, { compat: Verifier.isCompat(this.header.key, manifest), crypto: this.crypto, legacy: this._legacy })
683+
const verifier = this.verifier || new Verifier(this.header.key, manifest, { crypto: this.crypto, legacy: this._legacy })
685684

686685
if (!verifier.verify(batch, batch.signature)) {
687686
throw INVALID_SIGNATURE('Proof contains an invalid signature')
688687
}
689688

690689
if (!this.header.manifest) {
691-
this.compat = Verifier.isCompat(this.header.key, manifest)
692690
this.header.manifest = manifest
691+
this.compat = verifier.compat
693692
this.verifier = verifier
694693
this.onupdate(0b10000, null, null, null)
695694
}

lib/merkle-tree.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ class MerkleTreeBatch {
7979
return this.hashCached
8080
}
8181

82-
signable (namespace) {
83-
return caps.treeSignable(namespace, this.hash(), this.length, this.fork)
82+
signable (manifestHash) {
83+
return caps.treeSignable(manifestHash, this.hash(), this.length, this.fork)
8484
}
8585

8686
signableCompat (noHeader) {

lib/verifier.js

+42-19
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,35 @@ const multisig = require('./multisig')
99
const caps = require('./caps')
1010

1111
class Signer {
12-
constructor (crypto, index, { signature = 'ed25519', publicKey, namespace = caps.DEFAULT_NAMESPACE } = {}) {
12+
constructor (crypto, manifestHash, version, index, { signature = 'ed25519', publicKey, namespace = caps.DEFAULT_NAMESPACE } = {}) {
1313
if (!publicKey) throw BAD_ARGUMENT('public key is required for a signer')
1414
if (signature !== 'ed25519') throw BAD_ARGUMENT('Only Ed25519 signatures are supported')
1515

1616
this.crypto = crypto
17+
this.manifestHash = manifestHash
18+
this.version = version
1719
this.signer = index
1820
this.signature = signature
1921
this.publicKey = publicKey
2022
this.namespace = namespace
2123
}
2224

25+
_ctx () {
26+
return this.version === 0 ? this.namespace : this.manifestHash
27+
}
28+
2329
verify (batch, signature) {
24-
return this.crypto.verify(batch.signable(this.namespace), signature, this.publicKey)
30+
return this.crypto.verify(batch.signable(this._ctx()), signature, this.publicKey)
2531
}
2632

2733
sign (batch, keyPair) {
28-
return this.crypto.sign(batch.signable(this.namespace), keyPair.secretKey)
34+
return this.crypto.sign(batch.signable(this._ctx()), keyPair.secretKey)
2935
}
3036
}
3137

3238
class CompatSigner extends Signer {
3339
constructor (crypto, index, signer, legacy) {
34-
super(crypto, index, signer)
40+
super(crypto, null, 0, index, signer)
3541
this.legacy = legacy
3642
}
3743

@@ -45,16 +51,24 @@ class CompatSigner extends Signer {
4551
}
4652

4753
module.exports = class Verifier {
48-
constructor (manifest, { compat = false, crypto = defaultCrypto, legacy = false } = {}) {
54+
constructor (manifestHash, manifest, { compat = isCompat(manifestHash, manifest), crypto = defaultCrypto, legacy = false } = {}) {
55+
const self = this
56+
57+
this.manifestHash = manifestHash
4958
this.compat = compat || manifest === null
5059
this.version = this.compat ? 0 : typeof manifest.version === 'number' ? manifest.version : 1
5160
this.hash = manifest.hash || 'blake2b'
5261
this.allowPatch = !this.compat && !!manifest.allowPatch
5362
this.quorum = this.compat ? 1 : defaultQuorum(manifest)
54-
this.signers = manifest.signers
55-
? manifest.signers.map((s, index) => this.compat ? new CompatSigner(crypto, index, s, legacy) : new Signer(crypto, index, s))
56-
: []
63+
64+
this.signers = manifest.signers ? manifest.signers.map(createSigner) : []
5765
this.prologue = this.compat ? null : (manifest.prologue || null)
66+
67+
function createSigner (signer, index) {
68+
return self.compat
69+
? new CompatSigner(crypto, index, signer, legacy)
70+
: new Signer(crypto, manifestHash, self.version, index, signer)
71+
}
5872
}
5973

6074
_verifyCompat (batch, signature) {
@@ -129,21 +143,22 @@ module.exports = class Verifier {
129143
// TODO: better api for this that is more ... multisig-ey
130144
sign (batch, keyPair) {
131145
if (!keyPair || !keyPair.secretKey) throw BAD_ARGUMENT('No key pair was passed')
132-
if (this.signers.length > 1 || this.allowPatch) throw BAD_ARGUMENT('Can only sign directly for single signers')
133146

134-
const signature = this.signers[0].sign(batch, keyPair)
135-
if (this.version !== 1) return signature
136-
return this.assemble([{ signer: 0, signature, patch: 0, nodes: null }])
147+
for (const s of this.signers) {
148+
if (b4a.equals(s.publicKey, keyPair.publicKey)) {
149+
const signature = s.sign(batch, keyPair)
150+
if (this.signers.length !== 1 || this.version === 0) return signature
151+
return this.assemble([{ signer: 0, signature, patch: 0, nodes: null }])
152+
}
153+
}
154+
155+
throw new BAD_ARGUMENT('Public key is not a declared signer')
137156
}
138157

139158
assemble (inputs) {
140159
return this.version === 0 ? multisig.assemblev0(inputs) : multisig.assemble(inputs)
141160
}
142161

143-
manifestHash () {
144-
return manifestHash(this)
145-
}
146-
147162
static manifestHash (manifest) {
148163
return manifestHash(manifest)
149164
}
@@ -163,6 +178,11 @@ module.exports = class Verifier {
163178
}
164179
}
165180

181+
static fromManifest (manifest, opts) {
182+
const m = this.createManifest(manifest)
183+
return new this(manifestHash(m), m, opts)
184+
}
185+
166186
static createManifest (inp) {
167187
if (!inp) return null
168188

@@ -192,12 +212,11 @@ module.exports = class Verifier {
192212
}
193213

194214
static isCompat (key, manifest) {
195-
return !!(manifest && manifest.signers.length === 1 && b4a.equals(key, manifest.signers[0].publicKey))
215+
return isCompat(key, manifest)
196216
}
197217

198218
static sign (manifest, batch, keyPair, opts) {
199-
const v = new Verifier(manifest, opts)
200-
return v.sign(batch, keyPair)
219+
return Verifier.fromManifest(manifest, opts).sign(batch, keyPair)
201220
}
202221
}
203222

@@ -207,6 +226,10 @@ function toMap (nodes) {
207226
return m
208227
}
209228

229+
function isCompat (key, manifest) {
230+
return !!(manifest && manifest.signers.length === 1 && b4a.equals(key, manifest.signers[0].publicKey))
231+
}
232+
210233
function defaultQuorum (man) {
211234
if (typeof man.quorum === 'number') return man.quorum
212235
if (!man.signers || !man.signers.length) return 0

0 commit comments

Comments
 (0)