Sugriva's Covenant: The Divided Oath

By Team Cryptonite · 2 min read

About the CTF: Pinaka CTF

No lone captain commands the host. The covenant demands a quorum - but the ledger is damaged and the oath-seals are scattered across revisions. Validator: https://pinaka-k02-cyber-xo-x.vercel.app/

Sugriva's Covenant: The Divided Oath

**Pinaka CTF 2026** · Cryptography · Team Cryptonite · All 44 Pinaka write-ups


Challenge

> No lone captain commands the host. The covenant demands a quorum - but the ledger is damaged and the oath-seals are scattered across revisions.

> Validator: https://pinaka-k02-cyber-xo-x.vercel.app/

We're given README.md, params.json, covenant.py and covenant_log.json.

The setup is a threshold-BLS scheme over BLS12-381. A command is authorised only when 3 of the 5 commanders' oath-seals Lagrange-combine into a signature that verifies under the council key X. We have none of the secret shares. The job is to get the covenant to ratify a forbidden order (strike-order, "MARCH ON KISHKINDHA AT DAWN...") that no quorum ever sealed.

The weakness

Both the README and covenant.py keep pointing at what the token is bound to. The token is just:


def oath_token(command_id, epoch):
    return f"PINAKA-K02-OATH|cmd={command_id}|epoch={epoch}".encode()

Sugriva's Covenant: The Divided Oath: step screenshot

The body and the revision aren't in there. So a commander's seal on strike-order at epoch 7 is the same no matter which amendment it was attached to, or what the amendment's text actually was.

The ledger plays right into that. It leaks seals for commanders 1, 2, 4 and 5, taken from three amendments that all lapsed (revisions 4, 5 and 6). None of those amendments reached 3 signers on its own. But because the token ignores the body, all four leaked seals are valid partials on the exact same strike-order token. Four is already more than the threshold of 3.

Running the checks

covenant.py ships with a self-check, so I ran it first:


python3 covenant.py
seal of commander 1 (Hanuman) verifies: True
seal of commander 2 (Nala) verifies: True
seal of commander 4 (Tara) verifies: True
seal of commander 5 (Jambavan) verifies: True
combined 3-of-5 signature verifies under council key: True

Sugriva's Covenant: The Divided Oath: step screenshot

So any 3 of the leaked seals combine into a signature that verifies under the council key. That's the whole break.

Building the signature

I put the same steps into a short script so it prints the combined signature:


import covenant as C

p, L = C.load_params(), C.load_ledger()
tok = C.oath_token(L["protected_command_id"], p["council_epoch"])
X = C.deser_g1(p["council_public_key"])

seals = {int(i): C.deser_g2(v["oath_seal"]) for i, v in L["leaked_seals"].items()}
for i, s in seals.items():
    Xi = C.deser_g1(p["commander_verify_keys"][str(i)])
    print(i, C.verify_partial(Xi, s, tok))

quorum = dict(list(seals.items())[:p["threshold"]["t"]])
sig = C.combine(quorum)
assert C.bls_verify(X, sig, tok)
print("signature:", C.ser_g2(sig))

It rebuilds the strike-order token, deserialises the leaked seals, Lagrange-combines three of them (commanders 1, 2 and 4) at x = 0, verifies the result under the council key, and prints it:


token: PINAKA-K02-OATH|cmd=strike-order|epoch=7
1 True
2 True
4 True
5 True
signature: a6c927d5974ea254018ac3abe5ff53b2de1e07fd71d0999ea0ce1772d4f6f675dadf3343c8b538b0aad05ed389b574bb0465054a5a068539aba46e4c02383b694fba7847d862929f24989a53c35b8b5cb16b8ce38ae84c20f591ff2ffe8ccf4d

Sugriva's Covenant: The Divided Oath: step screenshot

Submitting

I submitted the forbidden body with that signature at a fresh revision (8, higher than the standing order's 3):


curl -X POST "https://pinaka-k02-cyber-xo-x.vercel.app/covenant/ratify" \
  -H "Content-Type: application/json" \
  -d '{
    "command_id": "strike-order",
    "revision": 8,
    "body": "UNSEAL THE DIVIDED OATH: MARCH ON KISHKINDHA AT DAWN AND SEIZE THE THRONE OF VALI",
    "signature": "a6c927d5974ea254018ac3abe5ff53b2de1e07fd71d0999ea0ce1772d4f6f675dadf3343c8b538b0aad05ed389b574bb0465054a5a068539aba46e4c02383b694fba7847d862929f24989a53c35b8b5cb16b8ce38ae84c20f591ff2ffe8ccf4d"
  }'
{"command_id":"strike-order",
 "flag":"PINAKA{sugriva_divided_oath_seals_scattered_across_revisions}",
 "proclamation":"The divided oath is unsealed; Kishkindha answers a command the council never gave.",
 "result":"ratified","revision":8}

Flag


PINAKA{sugriva_divided_oath_seals_scattered_across_revisions}

Summary

  1. The oath-token only binds the command id and the epoch, not the body or the revision. So a commander's seal on a slot is identical across every amendment.
  2. The ledger leaks seals from commanders 1, 2, 4 and 5, gathered from three different lapsed amendments. All of them are valid partials on the one strike-order token.
  3. Lagrange-combine any 3 of them with the public covenant.py to get a signature that verifies under the council key. No secret share needed.
  4. Submit the forbidden body with that signature at a fresh revision to ratify it and get the flag.

Read this post on CyberHx →