Skip to content

Multi sig witness#1028

Merged
AlCutter merged 3 commits into
transparency-dev:mainfrom
AlCutter:multi_sig_witness
Jul 2, 2026
Merged

Multi sig witness#1028
AlCutter merged 3 commits into
transparency-dev:mainfrom
AlCutter:multi_sig_witness

Conversation

@AlCutter

@AlCutter AlCutter commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

This PR improves the way the Tessera witness client handles policies which require multiple cosignatures from the same URL (e.g. a witness which is returning both ed25519 and MLDSA cosignatures).

Previously, such a policy would have caused multiple add-checkpoint requests to be sent to the witness, one for each vkey in the policy, and all but the first of those would be guaranteed to receive a 409 in response causing more requests to be sent.

With the changes in this PR, Tessera should make at most a single call to each witness URL present in the policy (conflicts not withstanding).

I've added a new WitnessEndpoint func on the witness policy interface so as to avoid breaking semver by changing the func signature. In practice, I doubt anyone uses the old Endpoints func directly (as opposed to just creating the policy structure and passing it directly to WithWitnesses), but...

@AlCutter
AlCutter requested a review from roger2hk July 1, 2026 11:34
@AlCutter
AlCutter requested a review from a team as a code owner July 1, 2026 11:34
@AlCutter
AlCutter force-pushed the multi_sig_witness branch 2 times, most recently from fc525c6 to b11b371 Compare July 1, 2026 17:43
seen := make(map[verifierKey]bool)
var dedup []note.Verifier
for _, v := range vs {
k := verifierKey{name: v.Name(), hash: v.KeyHash()}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could v be nil here? If so, add a defensive nil check.

    if v == nil {
        continue
    }

Comment thread internal/witness/client.go Outdated
@@ -266,10 +289,10 @@ func (pf *sharedConsistencyProofFetcher) ConsistencyProof(ctx context.Context, s
// This is defaulted to zero on startup and calibrated after the first request, which is expected by the spec:
// `If a client doesn't have information on the latest cosigned checkpoint, it MAY initially make a request with a old size of zero to obtain it`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// `If a client doesn't have information on the latest cosigned checkpoint, it MAY initially make a request with a old size of zero to obtain it`
// `If a client doesn't have information on the latest cosigned checkpoint, it MAY initially make a request with an old size of zero to obtain it`

Comment thread internal/witness/client.go Outdated
}
if recursed >= maxUpdateRecursion {
return nil, fmt.Errorf("too many consecutive requests to witness %s", w.verifier.Name())
return nil, fmt.Errorf("too many consecutive requests to witness %s", w.verifiers[0].Name())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would putting the first verifier in the error log be confusing later?

@AlCutter AlCutter Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory no, because the only use case for this currently is for a witness which returns multiple signatures, so all names should be identical. I've enforced this this is the case when parsing from policy at least.

That said, I could imagine this also supporting some sort of external witness gateway thing in the future, so I've changed to collate & uniq the names into a string here, and used that.

Comment thread internal/witness/client.go Outdated
if n, err := note.Open(signed, note.VerifierList(w.verifier)); err != nil {
return nil, fmt.Errorf("witness %q at %q replied with invalid signature: %q\nconstructed note: %q\nerror: %v", w.verifier.Name(), w.url, rb, string(signed), err)
if n, err := note.Open(signed, note.VerifierList(w.verifiers...)); err != nil {
return nil, fmt.Errorf("witness %q at %q replied with invalid signature: %q\nconstructed note: %q\nerror: %v", w.verifiers[0].Name(), w.url, rb, string(signed), err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would putting the first verifier in the error log be confusing later?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same)

@AlCutter
AlCutter force-pushed the multi_sig_witness branch from 5cdd19f to 8542623 Compare July 2, 2026 14:12
@AlCutter
AlCutter force-pushed the multi_sig_witness branch from 8542623 to cc90c30 Compare July 2, 2026 14:12
@AlCutter
AlCutter force-pushed the multi_sig_witness branch from cc90c30 to a1d10d7 Compare July 2, 2026 14:34
@AlCutter
AlCutter merged commit 35f1054 into transparency-dev:main Jul 2, 2026
19 of 20 checks passed
@AlCutter
AlCutter deleted the multi_sig_witness branch July 2, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants