Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion packages/wallet/core/src/signers/session-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ export class SessionManager implements SapientSigner {
}

// Perform encoding
const encodedSignature = SessionSignature.encodeSessionCallSignatures(
const encodedSignature = SessionSignature.encodeSessionSignature(
signatures,
await this.topology,
identitySigner,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export async function doEncodeSessionCallSignatures(
}
identitySigner = identitySigners[0]!
}
const encoded = SessionSignature.encodeSessionCallSignatures(
const encoded = SessionSignature.encodeSessionSignature(
callSignatures,
sessionTopology,
identitySigner as `0x${string}`,
Expand Down
88 changes: 87 additions & 1 deletion packages/wallet/primitives/src/session-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ export type SessionLeaf = SessionPermissionsLeaf | ImplicitBlacklistLeaf | Ident
export type SessionBranch = [SessionsTopology, SessionsTopology, ...SessionsTopology[]]
export type SessionsTopology = SessionBranch | SessionLeaf | SessionNode

const SESSIONS_NODE_SIZE_BYTES = 32

function isSessionsNode(topology: any): topology is SessionNode {
return Hex.validate(topology) && Hex.size(topology) === 32
return Hex.validate(topology) && Hex.size(topology) === SESSIONS_NODE_SIZE_BYTES
}

function isImplicitBlacklist(topology: any): topology is ImplicitBlacklistLeaf {
Expand Down Expand Up @@ -342,6 +344,90 @@ export function encodeSessionsTopology(topology: SessionsTopology): Bytes.Bytes
throw new Error('Invalid topology')
}

export function decodeSessionsTopology(bytes: Bytes.Bytes): SessionsTopology {
const { topology } = decodeSessionTopologyPointer(bytes)
return topology
}

function decodeSessionTopologyPointer(bytes: Bytes.Bytes): {
topology: SessionsTopology
pointer: number
} {
if (bytes.length === 0) {
throw new Error('Empty topology bytes')
}

const flagByte = bytes[0]!
const flag = (flagByte & 0xf0) >> 4
const sizeSize = flagByte & 0x0f

if (flag === SESSIONS_FLAG_BRANCH) {
// Branch
if (sizeSize === 0 || sizeSize > 15) {
throw new Error('Invalid branch size')
}

let offset = 1
const encodedLength = Bytes.toNumber(bytes.slice(offset, offset + sizeSize))
offset += sizeSize

const encodedBranches = bytes.slice(offset, offset + encodedLength)
const branches: SessionsTopology[] = []

let branchOffset = 0
while (branchOffset < encodedBranches.length) {
const { topology: branchTopology, pointer: branchPointer } = decodeSessionTopologyPointer(
encodedBranches.slice(branchOffset),
)
branches.push(branchTopology)
branchOffset += branchPointer
}

return { topology: branches as SessionsTopology, pointer: offset + encodedLength }
} else if (flag === SESSIONS_FLAG_PERMISSIONS) {
// Permissions
const sessionPermissions = decodeSessionPermissions(bytes.slice(1))
const nodeLength = 1 + encodeSessionPermissions(sessionPermissions).length
return { topology: { type: 'session-permissions', ...sessionPermissions }, pointer: nodeLength }
} else if (flag === SESSIONS_FLAG_NODE) {
// Node
const nodeLength = SESSIONS_NODE_SIZE_BYTES + 1
if (bytes.length < nodeLength) {
throw new Error('Invalid node length')
}
return { topology: Hex.fromBytes(bytes.slice(1, nodeLength)), pointer: nodeLength }
} else if (flag === SESSIONS_FLAG_BLACKLIST) {
// Blacklist
let offset = 1
let blacklistLength = sizeSize
if (sizeSize === 0x0f) {
// Size is encoded in the next 2 bytes
blacklistLength = Bytes.toNumber(bytes.slice(offset, offset + 2))
offset += 2
}

const blacklist: Address.Address[] = []
for (let i = 0; i < blacklistLength; i++) {
const addressBytes = bytes.slice(offset + i * 20, offset + (i + 1) * 20)
blacklist.push(Address.from(Hex.fromBytes(addressBytes)))
}

return { topology: { type: 'implicit-blacklist', blacklist }, pointer: offset + blacklistLength * 20 }
} else if (flag === SESSIONS_FLAG_IDENTITY_SIGNER) {
// Identity signer
const nodeLength = 21 // Flag + address
if (bytes.length < nodeLength) {
throw new Error('Invalid identity signer length')
}
return {
topology: { type: 'identity-signer', identitySigner: Address.from(Hex.fromBytes(bytes.slice(1, nodeLength))) },
pointer: nodeLength,
}
} else {
throw new Error(`Invalid topology flag: ${flag}`)
}
}

// JSON

export function sessionsTopologyToJson(topology: SessionsTopology): string {
Expand Down
100 changes: 89 additions & 11 deletions packages/wallet/primitives/src/session-signature.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { Address, Bytes, Hash, Hex } from 'ox'
import { Attestation, encode, encodeForJson, fromParsed, toJson } from './attestation.js'
import { MAX_PERMISSIONS_COUNT } from './permission.js'
import {
decodeSessionsTopology,
encodeSessionsTopology,
getIdentitySigners,
isCompleteSessionsTopology,
minimiseSessionsTopology,
SessionsTopology,
} from './session-config.js'
import { RSY } from './signature.js'
import { minBytesFor, packRSY } from './utils.js'
import { Payload } from './index.js'
import { minBytesFor, packRSY, unpackRSY } from './utils.js'
import { Attestation, Payload } from './index.js'

export type ImplicitSessionCallSignature = {
attestation: Attestation
attestation: Attestation.Attestation
identitySignature: RSY
sessionSignature: RSY
}
Expand Down Expand Up @@ -46,7 +46,7 @@ export function sessionCallSignatureToJson(callSignature: SessionCallSignature):
export function encodeSessionCallSignatureForJson(callSignature: SessionCallSignature): any {
if (isImplicitSessionCallSignature(callSignature)) {
return {
attestation: encodeForJson(callSignature.attestation),
attestation: Attestation.encodeForJson(callSignature.attestation),
identitySignature: rsyToRsvStr(callSignature.identitySignature),
sessionSignature: rsyToRsvStr(callSignature.sessionSignature),
}
Expand All @@ -68,7 +68,7 @@ export function sessionCallSignatureFromJson(json: string): SessionCallSignature
export function sessionCallSignatureFromParsed(decoded: any): SessionCallSignature {
if (decoded.attestation) {
return {
attestation: fromParsed(decoded.attestation),
attestation: Attestation.fromParsed(decoded.attestation),
identitySignature: rsyFromRsvStr(decoded.identitySignature),
sessionSignature: rsyFromRsvStr(decoded.sessionSignature),
}
Expand Down Expand Up @@ -113,7 +113,7 @@ function rsyFromRsvStr(sigStr: string): RSY {
* @param identitySigner The identity signer to encode. Others will be hashed into nodes.
* @returns The encoded session call signatures.
*/
export function encodeSessionCallSignatures(
export function encodeSessionSignature(
callSignatures: SessionCallSignature[],
topology: SessionsTopology,
identitySigner: Address.Address,
Expand Down Expand Up @@ -151,10 +151,12 @@ export function encodeSessionCallSignatures(
// Map each call signature to its attestation index
callSignatures.filter(isImplicitSessionCallSignature).forEach((callSig) => {
if (callSig.attestation) {
const attestationStr = toJson(callSig.attestation)
const attestationStr = Attestation.toJson(callSig.attestation)
if (!attestationMap.has(attestationStr)) {
attestationMap.set(attestationStr, encodedAttestations.length)
encodedAttestations.push(Bytes.concat(encode(callSig.attestation), packRSY(callSig.identitySignature)))
encodedAttestations.push(
Bytes.concat(Attestation.encode(callSig.attestation), packRSY(callSig.identitySignature)),
)
}
}
})
Expand All @@ -169,7 +171,7 @@ export function encodeSessionCallSignatures(
for (const callSignature of callSignatures) {
if (isImplicitSessionCallSignature(callSignature)) {
// Implicit
const attestationStr = toJson(callSignature.attestation)
const attestationStr = Attestation.toJson(callSignature.attestation)
const attestationIndex = attestationMap.get(attestationStr)
if (attestationIndex === undefined) {
// Unreachable
Expand All @@ -193,7 +195,83 @@ export function encodeSessionCallSignatures(
return Bytes.concat(...parts)
}

// Helper
export function decodeSessionSignature(encodedSignatures: Bytes.Bytes): {
topology: SessionsTopology
callSignatures: SessionCallSignature[]
} {
let offset = 0

// Parse session topology length (3 bytes)
const topologyLength = Bytes.toNumber(encodedSignatures.slice(offset, offset + 3))
offset += 3

// Parse session topology
const topologyBytes = encodedSignatures.slice(offset, offset + topologyLength)
offset += topologyLength
const topology = decodeSessionsTopology(topologyBytes)

// Parse attestations count (1 byte)
const attestationsCount = Bytes.toNumber(encodedSignatures.slice(offset, offset + 1))
offset += 1

// Parse attestations and identity signatures
const attestations: Attestation.Attestation[] = []
const identitySignatures: RSY[] = []

for (let i = 0; i < attestationsCount; i++) {
// Parse attestation
const attestation = Attestation.decode(encodedSignatures.slice(offset))
offset += Attestation.encode(attestation).length
attestations.push(attestation)

// Parse identity signature (64 bytes)
const identitySignature = unpackRSY(encodedSignatures.slice(offset, offset + 64))
offset += 64
identitySignatures.push(identitySignature)
}

// Parse call signatures
const callSignatures: SessionCallSignature[] = []

while (offset < encodedSignatures.length) {
// Parse flag byte
const flagByte = encodedSignatures[offset]!
offset += 1

// Parse session signature (64 bytes)
const sessionSignature = unpackRSY(encodedSignatures.slice(offset, offset + 64))
offset += 64

// Check if implicit (MSB set) or explicit
if ((flagByte & 0x80) !== 0) {
// Implicit call signature
const attestationIndex = flagByte & 0x7f
if (attestationIndex >= attestations.length) {
throw new Error('Invalid attestation index')
}

callSignatures.push({
attestation: attestations[attestationIndex]!,
identitySignature: identitySignatures[attestationIndex]!,
sessionSignature,
})
} else {
// Explicit call signature
const permissionIndex = flagByte
callSignatures.push({
permissionIndex: BigInt(permissionIndex),
sessionSignature,
})
}
}

return {
topology,
callSignatures,
}
}

// Call encoding

export function hashCallWithReplayProtection(
payload: Payload.Calls,
Expand Down
Loading