-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a Proof Type for Merkle Proof Selective Disclosure to JPA #15
Comments
Another option might be to put the "signature for the merkle root"... on the end of the "proof" object... So instead of this: Option 1
We would have this: Option 2
Downside of this would be constantly splitting the signature from the proofs when deriving.... if thats acceptable, we could also do: Option 3
|
I prefer Option 1 |
While it's not as ideal from a parsing perspective, Option 3 is I believe the correct abstraction. The challenge is really in the interface to the JWA, the implementing algorithm isn't expected to know or parse the actual protected header, it is encoded before even reaching it (see JWS 5.1). The only part that the algorithm has full control over is the one that it generates, the signature/proof, and this value is opaque to the application layer and preserved as an encoded byte array to be passed back in for validation un-altered. The merkle algorithm will need to encode everything it requires both to generate a disclosure and to validate the disclosed into the singular proof value. I think it's completely legit for that to still be a JSON object, but to the higher JWP layer it will be an opaque base64url encoded octet string. My pseudocode version of your Option 3 would look like: merkle_proof = {
"proofs":[ { path, nonce}, ...]
"root":"SflKxwRJSM...",
"signature":"ZflKxwRJSM..."
}
proof_b64 = base64url(JSON.stringify(merkle_proof))
message1_b64 = base64url(JSON.stringify({message}))
header = {
"kid": "did:example:123#key-0",
"alg": "MDP+ES256+JP",
"zip": "DEF"
}
header_b64 = base64url(JSON.stringify(merkle_proof))
jwp_json = {
"protected": header_b64,
"payloads": [ message1_b64, ...]
"proof": proof_b64
}
jwp_compact = header_b64 + "." + jwp_json.payloads.join("~") + "." + proof_b64 |
@quartzjer thanks, this makes a lot of sense, I will try and revise my proposal to align with it better. |
@quartzjer I finally got some time to hack this out... I opted not to provide an I like that it works with regular boring JWKs and JWS. I don't love the constant base64url encoding and decoding... or the current header configurations. I also don't love that I had to invent a compact encoding for merkle proofs in JSON... I think a reasonable next step would be to generalize from arrays of payloads to objects, and start attempting to use My naive plan for that is something like this:
Here is an example of JWP that only supports array types... {
"protected": {
"kid": "did:example:123#key-0",
"alg": "MDP+EdDSA",
"zip": "DEF"
},
"payloads": ["YQ", "Yg", "Yw"],
"proof": "eyJyb290IjoiYzllYjU1YWU4OTk0M2Q0MDRmY2U2MzI3MTM4MTAxYzgwYjIyYzcxYmIzYTY2NmFkZTBiYWY5YTRiZmIzN2JjNiIsIm5vbmNlIjoidXJuOnV1aWQ6ZTJjYWRlMDQtMjg5Ny00YTUwLTk4NDItY2QyZGQ5ZGVhZTEwIiwicHJvb2ZzIjoiZUp5VnpOc1NRa0FBQU5CLzhhcVpWY3VXUitSU2E1bHFpRzE2MEtJaHViUlc2dXY3aHM0SG5JdGtSR1VEcmZaTVN3cEgwbmFkb3BwNTdDSVdlRUh5R2dvODFUalYyWWFNV0JoRjVhSVBxaWZ0cUowMk9lcWhTOEptV0hKTWsva2didThQMFRQZVFmWlVIR2toR1k0UWRtbHoyTis5VVhqeWlFQmY3Unl3cFpWaHpvU3pDQUtWTC8xY2U0Qi9hNnZPdUZ4UXdOOWl6VHI5UlRQUDlhTVkweUJZN1htVWhtMWMyZGJjRHVJclhYOGQra3ZpIiwic2lnbmF0dXJlIjoiQlVVWHZ5VE9LS0hiSHBYUTMydlc5bFJzRVB2WEZsVkF5a3l2YTFIV0pLVUdMTExyV3NwMDN4aVdNWVdOclhweUJRYUhyZHdkWk5CcEp6a0dXSlowQ1EifQ"
}
in the future it would have: {
"protected": {
"kid": "did:example:123#key-0",
"alg": "MDP+EdDSA",
"zip": "DEF",
"lyt": { ... } // and this can be used to return a verified object instead of verified arrays.
},
"payloads": ["YQ", "Yg", "Yw"],
"proof": "eyJyb290IjoiYzllYjU1YWU4OTk0M2Q0MDRmY2U2MzI3MTM4MTAxYzgwYjIyYzcxYmIzYTY2NmFkZTBiYWY5YTRiZmIzN2JjNiIsIm5vbmNlIjoidXJuOnV1aWQ6ZTJjYWRlMDQtMjg5Ny00YTUwLTk4NDItY2QyZGQ5ZGVhZTEwIiwicHJvb2ZzIjoiZUp5VnpOc1NRa0FBQU5CLzhhcVpWY3VXUitSU2E1bHFpRzE2MEtJaHViUlc2dXY3aHM0SG5JdGtSR1VEcmZaTVN3cEgwbmFkb3BwNTdDSVdlRUh5R2dvODFUalYyWWFNV0JoRjVhSVBxaWZ0cUowMk9lcWhTOEptV0hKTWsva2didThQMFRQZVFmWlVIR2toR1k0UWRtbHoyTis5VVhqeWlFQmY3Unl3cFpWaHpvU3pDQUtWTC8xY2U0Qi9hNnZPdUZ4UXdOOWl6VHI5UlRQUDlhTVkweUJZN1htVWhtMWMyZGJjRHVJclhYOGQra3ZpIiwic2lnbmF0dXJlIjoiQlVVWHZ5VE9LS0hiSHBYUTMydlc5bFJzRVB2WEZsVkF5a3l2YTFIV0pLVUdMTExyV3NwMDN4aVdNWVdOclhweUJRYUhyZHdkWk5CcEp6a0dXSlowQ1EifQ"
}
|
There'll be some updates in other issues/threads here about the layout and claims mapping which would make your next step drastically simpler.
Yeah, I can definitely see that. While I know your initial tooling here is JS/TS, if defining an algorithm I would actually lean towards doing it like all other crypto algorithms do in their signature/proof value: packed binary values. The reason it's predominate is it minimizes the attack surface on the format (there is no format to attack) and removes all external dependencies from the essential validation logic. The proof value here could be the binary octets of |
@quartzjer yep, we are already partially doing that here... https://github.com/OR13/jwp-mdt/blob/main/ref-impl/merkle.js#L41 I am familiar with TLV from NFC space, I could easily implement that approach... this is a very good suggestion. |
big fan of those kind of well defined data packs - they work well with go and c structs, and are generally very portable and rapid to process |
After thinking about this a bit more, you wouldn't want to do this... mostly because We can still get a more compact proof value though, just like this:
|
const base64url = require("base64url");
const { expandProofs } = require("./merkle");
const expandedDerivedJwp = {
protected: { kid: "did:example:123#key-0", alg: "MDP+EdDSA", zip: "DEF" },
payloads: ["Yg"],
proof:
"eyJyb290IjoiYzllYjU1YWU4OTk0M2Q0MDRmY2U2MzI3MTM4MTAxYzgwYjIyYzcxYmIzYTY2NmFkZTBiYWY5YTRiZmIzN2JjNiIsIm5vbmNlIjoidXJuOnV1aWQ6ZTJjYWRlMDQtMjg5Ny00YTUwLTk4NDItY2QyZGQ5ZGVhZTEwIiwicHJvb2ZzIjoiZUp4RnpMc09nakFVQU5CLzZhb0pscllYNm9aUjFDZytZMkkwRHREYkdud1ZMU2pFK08rNnVaM3A3TjhFYzZjdTFtbWMyWnZTcEV1a0ZpcWpQZ011NFNjWmNDcU5DVU9sZzVScFJnVmtraG5BanRJK3BnWWs4c3lFRW8yZlVrNHBrdmIvWER5c05iOHppcXRxWUFhT0ZjZFJXWTFhSlhoRlBvNjkvaTZQZW5YaTFJWjUzTkVwaXJNWDZYd0lEWnllWWlYV0lVTEJoc244Y3FkdXN0dld5eXA3TllsTW5XWHEyb25KNS9BRk45Zzl3Zz09Iiwic2lnbmF0dXJlIjoiQlVVWHZ5VE9LS0hiSHBYUTMydlc5bFJzRVB2WEZsVkF5a3l2YTFIV0pLVUdMTExyV3NwMDN4aVdNWVdOclhweUJRYUhyZHdkWk5CcEp6a0dXSlowQ1EifQ",
};
const tags = {
root: "00",
nonce: "01",
signature: "02",
proofs: "03",
};
const alternateProofEncoding = (jwp) => {
const decodedProof = JSON.parse(base64url.decode(jwp.proof));
const root = Buffer.from(decodedProof.root, "hex");
const nonce = Buffer.from(decodedProof.nonce);
const signature = Buffer.from(decodedProof.signature, "base64");
const proofs = Buffer.from(decodedProof.proofs, "base64");
const buff = Buffer.concat([
Buffer.concat([
Buffer.from(tags.root, "hex"),
Buffer.from(root.length.toString(16), "hex"),
root,
]),
Buffer.concat([
Buffer.from(tags.nonce, "hex"),
Buffer.from(nonce.length.toString(16), "hex"),
nonce,
]),
Buffer.concat([
Buffer.from(tags.signature, "hex"),
Buffer.from(signature.length.toString(16), "hex"),
signature,
]),
Buffer.concat([
Buffer.from(tags.proofs, "hex"),
Buffer.from(proofs.length.toString(16), "hex"),
proofs,
]),
]);
return {
...jwp,
proof: base64url.encode(buff),
};
};
it("encode as TLV", () => {
console.log(expandedDerivedJwp.proof.length); // 646
const jwp2 = alternateProofEncoding(expandedDerivedJwp);
console.log(jwp2.proof.length); // 436
}); |
I took a stab at a “JWP” encoding of merkle disclosure proofs in a token format….
https://or13.github.io/jwp-mdt/#name-root-proof
The main thing I noticed was some awkwardness regarding nesting JWS inside the protected header.
it feels like JWP “Protected Header” should reserve a member “signature” and its value should not contain concatenated “header” or “payload” attributes…
Otherwise if we reserved a member “jws” we would have an encoded JWS header, nested insider an encoded JWP header, each with potentially different alg zip kid, etc…
For merkle proofs a signature over the root must be disclosed, so we need a "signature" in the "protected header".
The text was updated successfully, but these errors were encountered: