Extend authorizeEntry signing callbacks: payload argument and custom signature ScVals - #1549
Conversation
There was a problem hiding this comment.
Pull request overview
Adds WebAuthn/passkey authorization support for Soroban entries.
Changes:
- Extends signing callbacks with payload and custom
ScValsupport. - Adds secp256r1 normalization and WebAuthn signature builders.
- Adds tests, exports, dependencies, and documentation.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/base/auth.ts |
Extends authorization callbacks and custom signatures. |
src/base/webauthn.ts |
Implements WebAuthn signature helpers. |
src/base/index.ts |
Exports the new APIs. |
test/unit/base/auth.test.ts |
Tests callback and custom-signature behavior. |
test/unit/base/webauthn.test.ts |
Tests normalization and ScVal construction. |
package.json |
Adds the test dependency. |
pnpm-lock.yaml |
Locks the dependency versions. |
docs/reference/core-transactions.md |
Documents WebAuthn APIs. |
docs/reference/core-soroban-primitives.md |
Updates authorization API reference. |
CHANGELOG.md |
Records the new functionality. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return nativeToScVal( | ||
| { | ||
| authenticator_data: toBytes(parts.authenticatorData), | ||
| client_data_json: toBytes(parts.clientDataJSON), | ||
| signature, |
| ) { | ||
| signature = toBuffer(sigResult.signature); | ||
| publicKey = sigResult.publicKey; | ||
| sigResult = await signer(preimage, payload); |
| if (sig.length === 64) { | ||
| let der: [bigint, bigint] | null = null; | ||
| if (sig[0] === 0x30) { | ||
| try { | ||
| der = parseDerSignature(sig); | ||
| } catch { |
# Conflicts: # CHANGELOG.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
| function signPayloadCallback(preimage, payload) { | ||
| // `payload` is hash(preimage.toXDR()) — inspect `preimage` if you want | ||
| // to display/verify what is being authorized before signing. | ||
| return signer.sign(payload); | ||
| } |
There was a problem hiding this comment.
In this example, do we need to pass in preimage if we don't use it? It's only mentioned in the comment.
There was a problem hiding this comment.
Callbacks to functions can be omitted. In the case of the preimage param you would need to pass an _ if you still needed access to payload.
| export type SigningCallback = ( | ||
| preimage: xdr.HashIdPreimage, | ||
| ) => Promise<BufferLike | { signature: BufferLike; publicKey: string }>; | ||
| payload: Buffer, |
There was a problem hiding this comment.
| payload: Buffer, | |
| payload?: Buffer, |
Can we make this optional so the signature is non-breaking?
There was a problem hiding this comment.
Did further digging since our convo. The params of a function callback can be omitted
| await expect( | ||
| authorizeEntry( | ||
| authEntry, | ||
| () => |
There was a problem hiding this comment.
Can you try actually assigning the callback like let callback: SigningCallback = () { ... } and see if it compiles? If it does then we don't need the optional.
There was a problem hiding this comment.
I also verified locally that this works
There was a problem hiding this comment.
Are we sure this hand-rolled cryptography belongs in the SDK? Can we use a library or have it live elsewhere?
There was a problem hiding this comment.
Yeah I would agree that it does not belong. Thats been reverted
|
|
||
| // Differential check against @noble/curves (a well-reviewed reference | ||
| // implementation): for many real P-256 signatures, our DER parsing + | ||
| // low-S normalization must agree byte-for-byte with noble's. |
There was a problem hiding this comment.
If noble does this, lets use noble?
What
Extends
authorizeEntry/authorizeInvocationsigning callbacks in two ways:hash(preimage.toXDR())) as a second argument alongside the preimage, so signers that only accept a digest — HSMs, remote signers — never have to re-derive it. Existing single-argument callbacks are unaffected (#1532).{ signatureScVal: xdr.ScVal, address?: string }instead of a raw 64-byte signature. The given ScVal is written verbatim as the credentials' signature — no Ed25519 verification, no{public_key, signature}map, noscvVecwrapping. The optionaladdressroutes the signature to a specific credential node, likeforAddress(#1530).Why
Custom account contracts (smart wallets, passkey/WebAuthn signers) define their own signature structure in
__check_auth, so the SDK's Ed25519-only callback contract forced them to hand-roll preimage construction and credential assembly. With these two changes,authorizeEntryhandles the preimage, expiration, and credential plumbing while the caller supplies whatever signature ScVal their contract expects.The concrete WebAuthn helpers (
buildWebAuthnSignatureScVal,normalizeSecp256r1Signature) were originally part of this PR but have been reverted; this PR now ships only the generic callback extensions.