1
I Use This!
Very High Activity

Commits : Listings

Analyzed about 19 hours ago. based on code collected 2 days ago.
Nov 06, 2025 — Nov 06, 2026
Commit Message Contributor Files Modified Lines Added Lines Removed Code Location Date
Take the two working buffers a node hash is keyed and masked in from the caller as well - one pair for a whole tree walk rather than a pair per node. Neither carries anything between nodes: PRF fills all n bytes of key before H reads it as a key, and fills both n-byte halves of tmpMask before maskInto folds the two children into them and H hashes the result, so what the previous node left in either is gone before it is read - the same argument that lets WOTSPlus.chain take its two, and randomizeHash was the other end of it. Only out has to stay one array per call: it becomes the returned node's value and a walk keeps the nodes it makes - the L-tree's own array, and a BDS state's stack, authentication path, retain and keep - so one shared across a walk would leave every one of them holding the last node computed. The pair is allocated at the top of each of the four walks, being BDS.initialize, BDS.nextAuthenticationPath, BDSTreeHash.update and XMSSVerifierUtil.getRootNodeFromSignature, and lTree hands its caller's on to every node of an L-tree; the last two own theirs rather than taking one from further out, because a walk is at least 66 calls at n = 32 against a pair of 128 bytes and threading them up through BDSTreeHash.update and XMSSEngine would buy back a pair per call of those. An h=10 SHA-256 key generation hashes 68607 interior nodes over its 1024 leaves, 66 of them per L-tree and 1023 for the tree above, and had been allocating both buffers for every one; it now allocates four pairs in total, and goes from 23425994 to 14644538 bytes, 22.34 MiB to 13.97 MiB, with signing 37106 to 29114. At SHA-512 it is 70924344 to 41024216, 67.64 MiB to 39.12 MiB, and signing 107112 to 78600. Measured against the tree as it stood before the previous commit, in the same harness and the same run, the two together take key generation from 36402122 to 14644538 at SHA-256 and from 109262904 to 41024216 at SHA-512. The results are unchanged and are pinned as such rather than assumed: XMSSPromotionCompatibilityTest compares keys, signatures and cross-verification against the deprecated org.bouncycastle.pqc.crypto.xmss copy over six parameter sets and every index of a key, and passes, as do crypto.signers.xmss.AllTests, the provider's XMSSTest and XMSSMTTest and pqc.crypto.test.AllTests. Four mutations confirm that battery reaches the reasoning above - key and tmpMask made the same array on the key generation path and on the verification path, out shared across a walk the way the other two now are, and the second bitmask written over the first so that the top half of tmpMask keeps what the previous node left there - and two confirm the sharing is sound: filling both with 0xFF at the top of every call moves nothing, and neither does ignoring the caller's pair and allocating a fresh one per call, which is what this code did before and so is the statement that sharing changed no answer. More... 2 months ago
Take a WOTS+ chain's two working buffers, and the index its starting secret key is derived from, from the caller - one set for the len chains of a one-time key rather than one set per chain. Neither of chain()'s two carries anything between steps, let alone between chains: PRF fills all n bytes of key, and fills all n of tmpMasked before xorTo folds the running value into it, so what a previous chain left in either is gone before it is read, and the same is true of expandSecretKeySeed's two - the 32-byte toByte(index, 32) it built per chain differs from the last one only in its final four bytes, over a buffer whose other 24 are the zeros a fresh array already carries, and the n-byte PRF result is overwritten in full. Only out, which is the chain's answer, has to stay one array per call: the caller collects the len returns in a byte[][] that WOTSPlusSignature and WOTSPlusPublicKeyParameters clone afterwards, so one shared across a key's chains would leave every entry holding the last chain's value. What made a shared startHash buffer possible alongside them is the one behavioural change here: a zero-step chain now copies startHash into out instead of handing startHash itself back, so chain() returns an array of its own whatever steps is and no caller has to know that at one particular base-w digit its buffer would instead escape into a signature. That costs the allocation expandSecretKeySeed used to make for that same chain, and it lets WOTSPlusSignature.getBlock() drop the aliasing case its javadoc had to reason about. An h=10 SHA-256 key generation walks 68608 chains over its 1024 leaves and had been allocating four arrays for every one of them; it now allocates four per leaf, and goes from 36402122 to 23426106 bytes, 34.72 MiB to 22.34 MiB, with signing 61874 to 37434. At SHA-512 it is 109262904 to 71072024, 104.20 MiB to 67.78 MiB, and signing 179432 to 107496. The results are unchanged and are pinned as such rather than assumed: XMSSPromotionCompatibilityTest compares keys, signatures and cross-verification against the deprecated org.bouncycastle.pqc.crypto.xmss copy over six parameter sets and every index of a key, and passes, as do crypto.signers.xmss.AllTests, the provider's XMSSTest and XMSSMTTest and pqc.crypto.test.AllTests. Five mutations confirm that battery reaches the new reasoning - the old zero-step return with the buffer now shared, the index written a byte early, key and tmpMasked made the same array on the key generation path and on the verification path, and the index buffer's leading zeros poisoned - and two more confirm the reuse is sound, filling key and tmpMasked with 0xFF at the top of every step and startHash before every derivation, neither of which moves a byte. The index-encoding pair are the interesting ones: both pass all 83 of crypto.signers.xmss.AllTests, because a round trip agrees with itself whichever bytes it hashes, and only the comparison with the deprecated copy sees them. More... 2 months ago
Corrected the examples section of docs/claude/architecture.md: the pg OpenPGP examples package moved to misc in 06152ac3e2, so no published module carries an examples package under src/main/java, and the remaining javadoc / j2me / jdk1.1 examples trees are noted as deliberate. More... 2 months ago
OpenPGP: a truncated message is now reported rather than read as a clean end of message - the AEAD decryption streams no longer let an EOFException escape to BCPGInputStream.nextPacketTag(), and OpenPGPMessageInputStream.close() now closes its layer's integrity-protected stream so the SEIPDv1 MDC is always verified. More... 2 months ago
Walk the L-tree over a WOTS+ public key, and the tree above the leaves, over one address encoding each rather than rebuilding an address and re-encoding it per node. randomizeHash takes the 32-byte encoding instead of the address, which is what lets a walk keep one: it is 66 calls per L-tree at n = 32 and another 1023 for a tree of height 10, and every one of them built an LTreeAddress or a HashTreeAddress - immutable, so changing a field is a whole rebuild - and took a toByteArray() of it inside. lTree takes the encoding as well, so its caller supplies one, which in BDS.initialize is one for all 1024 leaves with the leaf written into the word that names it. The two words a walk moves, tree height and tree index, are now named on the two classes that lay them out, at the same two offsets in both because RFC 8391 sec. 2.5 gives every address type one 32-byte frame; the walks carry them as ints beside the encoding, so climbing a level is an increment rather than a read back out of the bytes. An h=10 SHA-256 key generation built 81921 addresses and took 70655 encodings of them, and now builds 2051 and takes 2050 - two of each per leaf, being the OTS hash address WOTSPlus derives the one-time key from and the one it takes the public key over, plus the two the tree walk starts from. Signing goes from 82 and 70 to 7 and 6. XMSSNodeUtil's four with-this-field-replaced helpers and BDS.withLTreeAddress go with the rebuilds, leaving withOTSAddress as the last of them, and the five getters that had no other caller go too, so LTreeAddress and HashTreeAddress are now what they are used as: a builder and an encoding. More... 2 months ago
Walk the len chains of a WOTS+ key over one address encoding rather than rebuilding an address and re-encoding it per chain, and hand the public seed to the tree walk by reference. The three loops that walk a key's chains - sign(), getPublicKey() and getPublicKeyFromSignature() - differ from one chain to the next in the chain address word alone, and an XMSS address being immutable that meant a Builder and an OTSHashAddress per chain, plus the toByteArray() chain() then took of it because PRF reads the address as the 32 bytes it is. An h=10 SHA-256 key generation walks 68608 chains, being 1024 leaves of len = 67, so those were 68608 addresses built and 67584 encodings taken of them; the loops now take one encoding per key and write the chain address into it, which is 1024 encodings, one per leaf, and 81921 addresses where there were 150529. chain() takes the encoding instead of the address and goes on writing the same two words it always wrote, at the top of every step, so what a previous chain left in them is overwritten before anything is hashed - and the steps == 0 return, the one path that writes nothing into the buffer, hashes nothing either. withChainAddress() goes with the rebuild, and the chain address offset is now named on the class that lays the encoding out rather than being the number 20 in two places. getPublicSeed() stops cloning: WOTSPlus is package-private, so no code outside org.bouncycastle.crypto.signers.xmss can name the type let alone call the method, and its one caller in the package, XMSSNodeUtil.randomizeHash, hands what it gets to PRF as a key and reads it nowhere else - which was a copy per interior node of every tree walked, 68607 of them in an h=10 key generation. The comment on importKeys() gave that clone, and a getSecretKeySeed() this class has not had for some time, as the reason it copies its arguments in; the reason is the one that was always doing the work, that the seeds arrive from a key object which goes on holding them. More... 2 months ago
Hash a pair of tree nodes out of one address encoding and one 2n-byte buffer rather than three addresses, three encodings and three PRF outputs, which is where most of what an XMSS key generation still allocated was going. randomizeHash makes three PRF calls per interior node - a key and two bitmasks - and they differ in one word of the address, so they now run over a single toByteArray() with that word written into it, the way chain() steps its two since e84219c4d1; the bitmasks are produced straight into the buffer they are masked in, where maskInto turns each half into the masked value, and only the key needs an array of its own, being the one of the three that H reads as a key rather than as the data it hashes. An h=10 key generation walks 68607 interior nodes, 67584 of them in the L-trees over the one-time public keys. withKeyAndMask() goes with the rebuild, and its fall-through goes with it: it rebuilt an LTreeAddress or a HashTreeAddress and returned any other address type unchanged, that is with key-and-mask left at whatever it already held, so the three PRFs would have been three of the same hash - writing the word into the encoding sets it for every type instead. Nothing changes today, no caller passing anything else, lTree an LTreeAddress and BDS, BDSTreeHash and XMSSVerifierUtil a HashTreeAddress; but the rule is now the one RFC 8391 sec. 4.1.5 states rather than one about which subtypes were thought of, and it is stated where the code is. XMSSNode.maskTo becomes maskInto and KeyedHashFunctions.H becomes its buffer form, each replacing rather than joining what was there, and the allocating F that f3eb32b85d left behind had already lost its last caller to that commit and goes too: a form nothing calls is not kept for symmetry. More... 2 months ago
Run a WOTS+ chain out of four buffers that last the chain rather than three fresh n-byte arrays a step, by giving F and PRF a form that writes into an array the caller owns. Each step produced a key, a bitmask and an F result, read each of them once and dropped it, and an h=10 SHA-256 key generation takes 1029120 steps: the bitmask is now produced straight into the array it is masked in, where xorTo turns it into the masked value rather than a third array holding that, and F writes into out, which the next step reads as tmp and folds into tmpMasked before F writes out again - so out's previous contents are dead by the time they are overwritten, and the reuse rests on that rather than on how coreDigest orders its own work. What the loop must not do is write through tmp, because tmp starts as the caller's startHash and on the verification path that array is the signature's own block, read in place since 100f5878bc - masking into it would recover a public key by corrupting the signature it was recovered from, which is what testPublicKeyFromSignatureDiffersForOtherDigest catches when the xorTo arguments are reversed. out stays one array per call for a related reason: the caller collects the len returns in a byte[][] that WOTSPlusSignature and WOTSPlusPublicKeyParameters clone afterwards, so one shared across a key's chains leaves every entry holding the last chain's value - 5 of the 6 compatibility cases, 11 of the 83 in crypto.signers.xmss.AllTests, 17 of the provider's 29 XMSS cases and 15 of its 25 XMSS^MT ones. KeyedHashFunctions gains no second implementation of anything: coreDigest's body is now the out-buffer form, and the allocating one is that form into an array it has just made, so what gets hashed cannot depend on which of the two a call site picked. More... 2 months ago
Walk a WOTS+ chain forward over one encoding of its address rather than rebuilding the address twice at every step, which is where most of what an XMSS key generation allocates was going. chain() moves exactly two words of the address as it steps - the hash address, and the key-and-mask twice, once for the key and once for the bitmask - and an XMSS address is immutable, so each of those three moves meant a Builder, an OTSHashAddress, and, because PRF takes the address as the 32 bytes it is, a fresh toByteArray() of the whole thing: ten objects a step, and an h=10 SHA-256 key generation takes 1029120 steps, being 1024 leaves of len = 67 chains of w - 1 = 15. Written as a loop over the encoding taken once, the three moves are three int writes into it, and the ten objects a step are three plus two for the whole chain. Nothing read the incoming value of either word - withHashAddress() overwrote both, which is what it took them as arguments for, and it had no other caller, so it goes with the recursion - and toByteArray() allocates what it returns, so the encoding is this method's own and the caller's address object is left as untouched as it was when chain() reassigned nothing but its own parameter. The recursion unwound from the back and the loop chains from the front, which is the same sequence of hash addresses in the same order, startIndex first and upwards from there; and the two argument checks, applied at every level to a startHash that is the same object throughout and to a startIndex + steps that is largest outermost, are those same two checks applied once. The two offsets are now named on the classes that lay the encoding out and cited from there rather than copied, the loop being a second writer of a format that until now had one. More... 2 months ago
Build a stored XMSS private key in the array it is handed back in, rather than copying its bulk through two intermediates on the way there, and read the BDS state's node values where they lie. Three copies of the same bytes stood between the encoder and its result: BDSStateCodec.encode() took the body out of its ByteArrayOutputStream and then Arrays.concatenate'd the 32-byte checksum onto it, copying the whole body a second time to append 32 bytes to it - the checksum's room is now reserved in the stream before the one copy that takes the encoding out of it, and the digest written straight into the tail of that copy, which is what checksumTo() replaces checksum() to allow, the verify side asking for the same bytes in a buffer of its own; the two key classes' toByteArray() then concatenated that result with a separately allocated fixed part, copying both a third time - the state is now encoded first, so the index, seeds and root are written directly into the array that is returned and the state arrives in it by one arraycopy; and writeNode() took XMSSNode.getValue()'s defensive copy of every node it wrote, a clone per authentication path, stack, retain and keep entry of every state encoded, only to ask its length and hand it to the stream, where getValueLength() and an encodeTo(OutputStream) beside the existing byte[] one do both without one - a DataOutputStream only reads what it is given, so nothing escapes either way. Measured alternating against HEAD with both sides compiled the same way, exactly reproducibly: producing an h=10 SHA-256 key's 1138-byte encoding allocates 7608 -> 5456 bytes, its SHA-512 sibling's 1906-byte encoding 12480 -> 8896, an XMSS^MT h=20/d=2 key's 7552 -> 5385 and h=20/d=4 6048 -> 4664, so between 23% and 29% off a path a stateful caller takes after every signature. The encodings themselves are byte for byte the ones before them at all four parameter sets, and the sign side is unchanged to the byte - 393 MiB per h=10 key generation and 1953 KiB per signature at n=32, identical across four alternating runs - none of this being on it. What is left on the encode path is the stream's own growth and the one copy out of it, which cannot go without the encoder computing its output's size up front, i.e. without a second statement of the format. More... 2 months ago
Read the XMSS tree walk's inputs where they lie rather than through a defensive copy of each, in the five places inside the package where the copy protected nothing: lTree deep-copied the whole WOTS+ public key it compresses, once per leaf, only to wrap each block in an XMSSNode that copies its value outwards anyway - WOTSPlusPublicKeyParameters.toNodes() does the wrapping instead, so the blocks are never copied; randomizeHash cloned both node values, once per interior node of every tree, only to feed them to Bytes.xor - XMSSNode.maskTo() masks straight from the value; getPublicKeyFromSignature deep-copied all len blocks of the signature, once per verification and again per layer of a hypertree, only to index one block out at a time - WOTSPlusSignature.getBlock() hands them over in place, which chain() only reads and WOTSPlusPublicKeyParameters clones again on the way out; the two signature classes' getRandom() cloned a field whose one caller copies it into a buffer immediately, in classes that cannot be reached from outside this package at all; and the four sites that build the key H_msg is applied under wrote it as an Arrays.concatenate of three pieces, one of which XMSSUtil.toBytesBigEndian allocated only for concatenate to copy in and drop - XMSSEngine.hMsgKey() writes idx_sig into the 3n-byte key in place, over the zeros it already carries, and gives the expression the name RFC 8391 sec. 4.1.9 gives it. Nothing that was not already escaping escapes: XMSSNode copies its value out through getValue() and encodeTo(), and both WOTS+ value classes clone what they are handed, which is what makes reading their blocks in place equivalent rather than merely cheaper. Measured through the public lightweight API at h=10, exactly reproducibly across runs: a key generation allocates 403 -> 343 MiB at n=32 and 1081 -> 952 MiB at n=64, a signature 1997 -> 1703 KiB and 3878 -> 3424 KiB. Wall time moves inside its own noise in both directions, so this is an allocation result and not a speed one, and the encoded public key is unchanged at both sizes under a deterministic RNG. The added test is the one that pins the last of those: XMSSPromotionCompatibilityTest signed exactly once per key, so idx_sig was zero in every H_msg key it ever compared and where those bytes sit was pinned by nothing - a sign-then-verify round trip cannot pin it either, since both directions of one implementation build the key the same way and would agree with each other while disagreeing with the other implementation. testSignaturesMatchAtEveryIndexOfAKey walks all sixteen indices of an h=4 XMSS key and an h=4/d=2 XMSS^MT key against the deprecated implementation, comparing each signature byte for byte, and it is what fails when the index is written one byte off - the five existing cases do not. Five mutation probes, each compiled into a copy of the package rather than into the tree: reversing toNodes() fails 5 of the 6 cases, shortening maskTo() by a byte 5, shifting getBlock() by one 4, zeroing getRandom() 3, and moving hMsgKey()'s index write one byte fails 1, the new one. More... 2 months ago
Hash toByte(i, n) out of a shared constant table rather than rebuilding it on every keyed hash: coreDigest opened each of F, H, H_msg and PRF with XMSSUtil.toBytesBigEndian(i, digestSize), an n-byte allocation plus an eight-byte Pack write of seven zero bytes and one value byte, for something that is a constant - the domain separator i is 0..3, so RFC 8391 sec. 2.4's toByte(i, n) is n-1 zero bytes followed by i, and toByte(i, n) is then the last n bytes of toByte(i, 64), which lets a single static byte[4][64] serve every admissible n by being hashed at offset 64 - digestSize. Every n that fits: WOTSPlusOid admits 64, 32 and, for the two SP 800-208 parameter sets, 24, and 64 is the bound the class javadoc already relied on for the truncating branch below. That removes one allocation per keyed hash, and there is one per chain step of every leaf's len chains, plus the L-tree and the tree hash above them - measured through the public lightweight API at h=10, a key generation allocates 698 -> 541 MiB at n=32 and 1691 -> 1180 MiB at n=64, and a signature 3467 -> 2687 KiB and 6075 -> 4235 KiB. Wall time is unchanged in every case, the digest dominating it, so this is an allocation result and not a speed one. The table is written only by its static initializer and read thereafter, so sharing it across instances says nothing about threads. Byte identity holds two ways: XMSSPromotionCompatibilityTest compares against the untouched deprecated implementation over five parameter sets including XMSS-SHAKE256_10_192, the n=24 case that is the only one exercising a non-32 offset, and a key generation under a deterministic RNG prints the same public key before and after at both n=32 and n=64. Corrupting the table's value byte fails four of that test's five cases, which is what says the table is the thing being read. More... 2 months ago
Replace bytesToXBigEndian by Pack.bigEndianToLong_Low More... 2 months ago
Refactor around toBytesBigEndian More... 2 months ago
Merge branch 'main' into xmss-promote-crypto-signers-pick More... 2 months ago
Rename the seed parameter of WOTSPlus.getWOTSPlusSecretKey, which carried the name of the field it is not: the field secretKeySeed is a one-time key's own seed, the parameter is the XMSS private key's SK_SEED that such a seed is derived from, and the two sit one identifier apart at every call site - wotsPlus.importKeys(wotsPlus.getWOTSPlusSecretKey(secretSeed, otsHashAddress), publicSeed) hands the derivation its input and installs the output as the field. Inside, the method reads khf.PRF(secretKeySeed, otsHashAddress.toByteArray()) while expandSecretKeySeed fifteen lines below reads khf.PRF(secretKeySeed, XMSSUtil.toBytesBigEndian(index, 32)) - the same expression against two different seeds, and with the field shadowed neither needs a this. to disambiguate, so nothing on either line says which one it is. secretSeed is what BDS and BDSTreeHash already call this value at four of the five call sites, and the javadoc, which documented only the address argument, now says whose seed it is. importKeys keeps its same-named parameter, where the name is the right one: that parameter is the field's incoming value, and its body writes this.secretKeySeed where it means the field. Compiled -g:none the class file is byte for byte the one before it, so nothing but the name and the comment has moved; with debug information on, the sole delta is this method's four-entry LineNumberTable, shifted by the five javadoc lines. More... 2 months ago
Put WOTSPlus.importKeys on the shared field size check rather than its own copy of it, splitting the size half of that check out from the allocate-when-absent half it does not want: a20b761bc3 collected six copies of this check into one implementation for the specific reason that they had drifted into saying two different things about the same mistake, and these two were a plain string literal duplicate of what the surviving one says - findable by grepping the message, not by looking for the callers of the check that owns it, so a reword there would have left this file behind exactly as before. What importKeys cannot take is the null branch: validateOrAllocate answers an absent value with a freshly allocated all-zero array, which is right for an optional field of a key being built and wrong here, where it would import an all-zero one-time key seed instead of failing, so validateSize is the size check on its own and null goes on failing as the dereference it is. The added test pins both halves - the message for a short, long and empty seed on each of the two arguments, and that an absent one raises rather than being quietly replaced - and passes identically against WOTSPlus and XMSSUtil as they stood, which is what says nothing but the location of the check has moved. More... 2 months ago
Let a key shard adopt the traversal state it was just handed rather than copying it a second time, on both families: extractKeyShard builds a state narrowed to the range the shard is for - new BDSStateMap(bdsState, index + usageCount - 1) on the multi-tree side, withMaxIndex on the single tree one - and then hands it to a builder whose whole job with that argument is to copy it, so the state a shard is built on was deep copied twice, authentication path, stack, retain, keep and tree hash instances and a fresh WOTS+ per layer, when the object being handed over was created by the expression handing it over and shared with nothing. The XMSS side had only had this since its own builder was given the copy its XMSS^MT sibling already made, which is to say the fix for one defect had quietly bought the other. The adopt route is package private and stays that way: what the public setter's copy is there to stop is a caller keeping the state it passed in, and a public way of asking the key to adopt one is that same defect offered as an option. That leaves the two key generation paths and the key decode factory, which build an exclusive state too but sit in other packages, still paying for the copy - once per key pair and once per key read, against a shard that can be taken once per signature, so the one that repeats is the one this reaches. Verified byte identical over a digest of the encoding of all 512 shards of an h=10 XMSS key and an h=10/d=2 XMSS^MT one: allocation over those 512 falls 1436662 KiB to 1203743 KiB and 847852 KiB to 710139 KiB, both about a sixth, while wall clock moves 3715ms to 3657ms and 2178ms to 2139ms - the roll each shard performs being a tree traversal step, which is what the clock is mostly measuring. More... 2 months ago
Write the copy BDS's four carry-on-from-a-previous-state constructors all make once, and have the four delegate to it: each held its own copy of the same nine line block - tree height, k, root, the cloned authentication path, retain, stack, tree hash instances and keep, and the index - and differed from the others only in the WOTS+ instance it ends up with, whether it keeps the maximum index and the used mark of the state it copies, and what it does after the copy, so those are the arguments and the rest is now shared. What four copies costs is that a field added to the traversal state has to be threaded through four places by hand, which has already happened once, for maxIndex; missing one of them leaves that constructor quietly building a state whose new field never came across, and it is not a problem review reliably catches, because two of the four end in validate() and two - the plain copy and the one the signer advances through, both on the per signature path - do not. Having one copy is the way not to have the problem; adding validate() to the other two would be paying for a check on every signature to catch a mistake that can no longer be made. The digest ternary that picks between a WOTS+ parameter set with an explicit size and one without, the other thing written twice here, becomes a named helper saying why a decoded state cannot take the WOTS+ parameters of the state it is copied from. Verified byte identical over a digest driving all four - a one use shard and the decode of it and the signature it is for, at each of 64 XMSS indices, and a state map copy read layer by layer beside the encoding and signature at each of 64 XMSS^MT indices - against BDS as it stood. More... 2 months ago
Put the one wire form an XMSS and an XMSS^MT public key share behind one implementation, instead of a line for line copy of it in each key class: RFC 8391 sec. 4.1.7 and sec. 4.2.5 give the two families the same shape - root then SEED, both n bytes - and NIST SP 800-208 sec. 5 puts the same four byte parameter set identifier in front of each, so the encoding differs between them only in which parameter set that identifier names, which is to say not at all in anything the codec does. The two copies had already had to be hand edited in lockstep once in this branch, taking the same two changes as separately written hunks, and nothing but review stood between a third edit and a family that reads or writes its public keys differently from its sibling. XMSSPublicKeyCodec is package private, the classes it serves being the public surface, and its name starts XMSS so the crypto/params/XMSS* excludes the jdk1.4 and jdk1.3 Ant builds already carry go on covering it. One thing changes on the way: the encoder sized its output from the parameter set's n and then copied by the arrays' own lengths, two ways of saying the same number that nothing kept in step, and the shared one takes both from the arrays. Verified byte identical over a digest of every branch of both families - the current encoding and the pre-RFC one that carries no identifier, the round trip back through withPublicKey, the root and seed getters, and the message each of four refused lengths raises - against the two copies as they stood. More... 2 months ago
Refuse a legacy BDS state map encoding that carries bytes past the maximum index its readObject reads, the check BDS.readObject already makes of its own data: the two were reworked side by side in the same pass - both moved off available(), which is an estimate of what can be read without blocking rather than an end of data test, and onto read(), which returns -1 only at the end of the object's data - and only one came away with the final "and nothing after it", so the byte for byte same appended garbage was an error on a BDS and a second valid encoding of the same state on a state map. The outer "unexpected data found at end of ObjectInputStream" in XMSSUtil.deserialize does not reach this: bytes inside the object's own block data are consumed by the read that recovers it, so the stream is already at its end by the time that check looks, and only readObject is positioned to tell. The blast radius is narrow, this being the pre-BDSStateCodec form that BC reads and never writes, and BDSStateCodec calling checkFinished for both of the current formats - which is what left this the one place the two families disagreed. The added test builds the real serialized form of each and widens its trailing block data record by a byte, which puts the byte inside the object rather than after it, then asserts both are refused and, first, that the unpadded encoding of each is still read: against the state map as it stood only the BDS half fails, which is the asymmetry itself. More... 2 months ago
PKIX: Fix CertPathValidator parsing of the ISIS-MTT dateOfCertGen extension (in the chain validity model), which was missing the unwrap of the extnValue OCTET STRING. A new ValidityModelTest covers the shell and chain validity models through the provider API. More... 2 months ago
Compare the two records an XMSS^MT private key keeps of its position every time it moves and every time it is written out, rather than only when one is read back in: the global index and the per-layer BDS traversal states say the same thing and are advanced by two separate statements, so nothing but the author of those statements holds them together, and the constructor's bdsState.validate(params, index) - the multi-tree half of the cross-check github #2414 added - only ever sees a key arriving. A key going out of step while it is held had no check at all, and what it costs is the failure RFC 8391 sec. 1.1 exists to prevent, since a key whose index was rolled back while its state stayed advanced signs a second message under a one-time key it has already used and that signature verifies. The check is now split: validate(XMSSMTParameters, long) is the structural walk plus a new public validateIndex(XMSSMTParameters, long) carrying the index half alone, which is what rollKey() runs after both of its branches and what toByteArray() runs before it writes the pair out - d map lookups and d integer comparisons, where re-walking every authentication path, stack and tree hash once per signature would learn nothing the constructor did not already establish. The layer walk now goes through the map's own synchronized get() rather than reaching into the backing TreeMap, which is the rule the rest of the class was brought onto, and it is reached with the key monitor already held, so the order stays key then map. The added tests are the two ends of it: a layer zero state kept across a roll and put back afterwards - what a half-restored key looks like from the inside, built through the same package-private door the signer uses - is refused by both the roll and the encoding, and neither refusal fires against the key parameters as they stood; and the half that matters more, a key walked across every index it has, encoded at each and decoded back into one that still signs verifiably, which is what says the allowance for a leaf index of 0 has not been narrowed by accident. BDSStateMap's public surface is asserted by OpaqueStateHandleTests, so the new read-only method is named there too. More... 2 months ago
Take the one clone of a WOTS+ signature that recovering a public key from it needs, rather than one per element inside the loop that reads it: toByteArray() deep-copies all len rows and getPublicKeyFromSignature asked for that copy once per iteration only to index a single row out of it, so a verification did len squared row copies where it needs len - 4489 rather than 67 for the SHA-256 parameter sets, 17161 rather than 131 for SHA-512, and that again for every layer of a hypertree on the XMSS^MT side, where the method runs once per layer. The hoist is behaviour-preserving: WOTSPlusSignature is immutable and nothing in the loop body can invalidate the copy, and while chain() hands its argument straight back for a zero-step chain, the rows returned that way are rows of this local copy and WOTSPlusPublicKeyParameters clones what it is given, so none of it escapes. Measured against the method as it stood, over a digest of every signature and public key encoding produced by 1920 XMSS and 1920 XMSS^MT verifications: byte identical output, XMSS 1938ms to 1877ms and XMSS^MT 5920ms to 5719ms, the modest time next to the row counts being what says the chain hashing rather than the copying is what a verification mostly spends its time on. More... 2 months ago
BCJSSE: Add boolean system property org.bouncycastle.jsse.useNamedGroupsOrder More... 2 months ago
BCJSSE: Provider default for useCipherSuitesOrder is now 'true' More... 2 months ago
Copy the traversal state the XMSS key builder is given rather than adopting it, the way the XMSS^MT builder already does, and correct the note there that gave the single tree side as the case needing no copy: the reason is narrower than XMSS^MT's but it is real, because while no layer states are installed into a single BDS, a signature still marks in place the state it spent - XMSSEngine's markUsed() - so two keys sharing one BDS share that record. One key's signature marks the other's state, and the key that has signed nothing is then refused as one that has already signed; it travels the other way too, a caller keeping the state it passed in watching its own copy marked by a key it handed it to. The copy goes through withMaxIndex with the parameter set's own digest, which is the shape extractKeyShard uses on this same field and which rebuilds the WOTS+ parameters a decoded state does not carry. The added test is the single tree mirror of testKeyBuilderCopiesTheTraversalStateItIsGiven: against the builder as it stood, the state handed to the second key is the same object, so the assertion that they differ fails outright and the mark travels with it. More... 2 months ago
Make a BDS state map safe to read while the key holding it signs, because getBDSState() hands the live map out and the XMSS^MT signer installs subtree states into that same map with put() as it descends the layers: copying a state map off a key that is signing - which is what the builder does with what the getter returned, the shape this suite's own testKeyBuilderCopiesTheTraversalStateItIsGiven uses - walked a TreeMap while it was being restructured, which is a ConcurrentModificationException where the walk notices and a torn read of the tree where it does not, the copy coming back with layers missing or with a null where a state should be and the key built on it unusable in a way that only surfaces when it next signs. Every route to the backing map now takes the map's own monitor, the copy constructor included, and getStateMap() hands the encoder a snapshot rather than the map itself; doGenerateMTSignature holds that same monitor for its whole descent and the roll that follows, so a copy taken beside a signature is coherent rather than merely intact - every layer from before it or every layer from after it, never a mixture. Lock order is always key then map, and nothing takes them the other way round, a state map holding no reference to a key. The added test copies the map of a key signing through all sixty-four of its indices and validates each layer it gets back; against the map and engine as they stood it fails with ConcurrentModificationException. More... 2 months ago
Give markUsed() the read side it never had, so that a key holding a traversal state which says it has already signed is refused by XMSSEngine rather than signing again: the mark was written by both generate paths, copied by every BDS copy constructor and carried in the encoding, and nothing anywhere consulted it - BDS.nextAuthenticationPath's own "index already used" guard cannot fire, because every path that advances a state builds a fresh object with used false and then calls that method on itself. A key can reach the engine marked all the same, and the way there is ordinary rather than adversarial: take the state off a live key with getBDSState(), let that key sign, and the object taken is the one the signature marked, since a signature marks its state and then replaces it. Build a key back around what was taken and it sits on the index that signature spent, which RFC 8391 sec. 1.1 makes a private key compromise - two verifying signatures under one WOTS+ key - not a merely surprising result. Both checks sit ahead of the try, as the exhausted and not-initialized ones do, so a refused signature does not reach the finally that rolls the key; the XMSS^MT one reads a new BDSStateMap.isUsed() covering layer zero, whose leaf signs the message, and makes the allowance BDSStateMap.validate(params, globalIndex) already makes for the first leaf of a subtree, where the signer builds layer zero fresh and the state carried over from the end of the previous subtree is legitimately marked. Without the two checks the added tests sign a second message under a spent one-time key on both families; the XMSS^MT test also walks a whole key across every subtree boundary in it, which is what says the allowance is not too narrow. More... 2 months ago
Lock the XMSS and XMSS^MT signers on themselves rather than on whichever private key their field is holding at the moment they read it, because the field is one they replace: both read privateKey into a local and only then synchronized on what they had read, so a generateSignature() that had taken the old key and a getUpdatedPrivateKey() that had installed a new one held two different monitors and excluded each other from nothing - and what came out of that is the one failure a stateful scheme cannot have. getUpdatedPrivateKey() hands the caller the key to persist after rolling it past the index the signer keeps for itself, so a signature already holding that same object then spends the index the caller has just been told is still unused; store it, restore it, and one WOTS+ one-time key signs two messages with both signatures verifying, which RFC 8391 sec. 1.1 exists to prevent. The key's own monitor is still taken inside, where it is what excludes a second signer initialised on the same key, and init() now writes the field under the same monitor the readers take. SignerConcurrencyTests asserts the range invariant rather than a timing one - the key handed back for storage must sit strictly past every index the signer has spent - and against the two signers as they stood it fails on both families at the first trial, roughly a third of 4000 trials violating it, against none after. More... 2 months ago