Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 10 additions & 22 deletions samples/frontend/src/steps/embeddedWallet/AuthenticateAndSign.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ const SANDBOX_WALLET_SIGNATURE = 'sandbox-valid-signature'
// Grid returns encryptedSessionSigningKey sealed to clientPublicKey.
// 5. HPKE-decrypt the session signing key with the client private key
// (DHKEM-P256 / HKDF-SHA256 / AES-256-GCM, base58check wire format).
// 6. ECDSA-sign payloadToSign bytes with the session key, DER-encode,
// base64-encode, and pass to step 8 as the Grid-Wallet-Signature header.
// 6. Build a Turnkey API-key stamp over payloadToSign with the session key
// and pass the base64url stamp to step 8 as Grid-Wallet-Signature.
//
// Sandbox flow (this is what runs by default):
// - Step 3 still triggers the real OS biometric prompt.
Expand Down Expand Up @@ -82,7 +82,7 @@ export default function AuthenticateAndSign({
// signature or the sandbox magic value below.
const assertion = (await navigator.credentials.get({
publicKey: {
challenge: base64urlToBytes(challenge.challenge),
challenge: new TextEncoder().encode(challenge.challenge),
userVerification: 'required',
timeout: 60_000,
},
Expand Down Expand Up @@ -192,22 +192,16 @@ async function decryptSessionSigningKey(
return new Uint8Array(plaintext)
}

// Sign payloadToSign bytes verbatim (Grid hashes them on its side as part of
// signature verification). DER-encoded ECDSA signature, base64-encoded.
// Build the Turnkey API-key stamp expected by Grid-Wallet-Signature.
function signPayload(sessionPrivateKey: Uint8Array, payloadToSign: string): string {
const msg = new TextEncoder().encode(payloadToSign)
const sig = p256.sign(msg, sessionPrivateKey, { format: 'der' })
return bytesToBase64(sig as Uint8Array)
}

function base64urlToBytes(s: string): ArrayBuffer {
const pad = '='.repeat((4 - (s.length % 4)) % 4)
const b64 = (s + pad).replace(/-/g, '+').replace(/_/g, '/')
const bin = atob(b64)
const buf = new ArrayBuffer(bin.length)
const view = new Uint8Array(buf)
for (let i = 0; i < bin.length; i++) view[i] = bin.charCodeAt(i)
return buf
const stamp = JSON.stringify({
publicKey: bytesToHex(p256.getPublicKey(sessionPrivateKey, true)),
scheme: 'SIGNATURE_SCHEME_TK_API_P256',
signature: bytesToHex(sig as Uint8Array),
})
return bytesToBase64url(new TextEncoder().encode(stamp))
}

function bytesToBase64url(bytes: Uint8Array): string {
Expand All @@ -216,12 +210,6 @@ function bytesToBase64url(bytes: Uint8Array): string {
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}

function bytesToBase64(bytes: Uint8Array): string {
let bin = ''
for (const b of bytes) bin += String.fromCharCode(b)
return btoa(bin)
}

function bytesToHex(bytes: Uint8Array): string {
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')
}
Loading