diff --git a/.changeset/v2-keychain-auth.md b/.changeset/v2-keychain-auth.md new file mode 100644 index 00000000..99ba7f89 --- /dev/null +++ b/.changeset/v2-keychain-auth.md @@ -0,0 +1,15 @@ +--- +"ox": minor +--- + +**Breaking (`ox/tempo`):** `KeyAuthorization.chainId` is now required. + +```diff + const authorization = KeyAuthorization.from({ + address, ++ chainId: 1n, + expiry: 1234567890, + type: 'secp256k1', + }) +``` + diff --git a/.changeset/v2-keychain-sig-type.md b/.changeset/v2-keychain-sig-type.md new file mode 100644 index 00000000..af83735a --- /dev/null +++ b/.changeset/v2-keychain-sig-type.md @@ -0,0 +1,5 @@ +--- +"ox": patch +--- + +**`ox/tempo`:** Added support for V2 keychain signature type (`0x04`) which binds the inner signature to the user account via `keccak256(0x04 || sigHash || userAddress)`. diff --git a/src/tempo/KeyAuthorization.test.ts b/src/tempo/KeyAuthorization.test.ts index 51471349..58133f91 100644 --- a/src/tempo/KeyAuthorization.test.ts +++ b/src/tempo/KeyAuthorization.test.ts @@ -56,6 +56,7 @@ describe('from', () => { test('default', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -69,6 +70,7 @@ describe('from', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -85,6 +87,7 @@ describe('from', () => { const authorization = KeyAuthorization.from( { address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -102,6 +105,7 @@ describe('from', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -126,6 +130,7 @@ describe('from', () => { const authorization = KeyAuthorization.from( { address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -143,6 +148,7 @@ describe('from', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -167,6 +173,7 @@ describe('from', () => { const authorization = KeyAuthorization.from( { address, + chainId: 1n, expiry, type: 'p256', limits: [ @@ -184,6 +191,7 @@ describe('from', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -214,6 +222,7 @@ describe('from', () => { const authorization = KeyAuthorization.from( { address, + chainId: 1n, expiry, type: 'webAuthn', limits: [ @@ -231,6 +240,7 @@ describe('from', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -263,6 +273,7 @@ describe('from', () => { test('with inline signature (secp256k1)', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -277,6 +288,7 @@ describe('from', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -301,6 +313,7 @@ describe('from', () => { const authorization = KeyAuthorization.from({ expiry: Hex.fromNumber(expiry), keyId: address, + chainId: '0x1', keyType: 'secp256k1', limits: [{ token, limit: '0x989680' }], signature: { @@ -314,7 +327,7 @@ describe('from', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", - "chainId": 0n, + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -338,6 +351,7 @@ describe('from', () => { test('multiple limits', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -355,6 +369,7 @@ describe('from', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -374,6 +389,7 @@ describe('from', () => { test('zero expiry (never expires)', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry: 0, type: 'secp256k1', limits: [ @@ -387,6 +403,7 @@ describe('from', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", + "chainId": 1n, "expiry": 0, "limits": [ { @@ -403,6 +420,7 @@ describe('from', () => { describe('fromRpc', () => { test('secp256k1', () => { const authorization = KeyAuthorization.fromRpc({ + chainId: '0x1', expiry: Hex.fromNumber(expiry), keyId: address, keyType: 'secp256k1', @@ -418,7 +436,7 @@ describe('fromRpc', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", - "chainId": 0n, + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -441,6 +459,7 @@ describe('fromRpc', () => { test('p256', () => { const authorization = KeyAuthorization.fromRpc({ + chainId: '0x1', expiry: Hex.fromNumber(expiry), keyId: address, keyType: 'p256', @@ -458,7 +477,7 @@ describe('fromRpc', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", - "chainId": 0n, + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -486,6 +505,7 @@ describe('fromRpc', () => { test('webAuthn', () => { const authorization = KeyAuthorization.fromRpc({ + chainId: '0x1', expiry: Hex.fromNumber(expiry), keyId: address, keyType: 'webAuthn', @@ -496,7 +516,7 @@ describe('fromRpc', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", - "chainId": 0n, + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -527,6 +547,7 @@ describe('fromRpc', () => { test('multiple limits', () => { const authorization = KeyAuthorization.fromRpc({ + chainId: '0x1', expiry: Hex.fromNumber(expiry), keyId: address, keyType: 'secp256k1', @@ -551,7 +572,7 @@ describe('fromRpc', () => { expect(authorization).toMatchInlineSnapshot(` { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", - "chainId": 0n, + "chainId": 1n, "expiry": 1234567890, "limits": [ { @@ -845,6 +866,7 @@ describe('getSignPayload', () => { test('default', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -858,13 +880,14 @@ describe('getSignPayload', () => { const payload = KeyAuthorization.getSignPayload(authorization) expect(payload).toMatchInlineSnapshot( - `"0x6d56b62bb5e94ca0206340e4bc1ece5a35e7ad31c30c6b98074d146d5c5de993"`, + `"0x5a3a9a67cc6b68eafd00fe3ffb1ea8755ef29cfa1d1c2f655efa61966ef248f7"`, ) }) test('with signature (signature should be ignored)', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -880,13 +903,14 @@ describe('getSignPayload', () => { // Should be same as without signature expect(payload).toMatchInlineSnapshot( - `"0x6d56b62bb5e94ca0206340e4bc1ece5a35e7ad31c30c6b98074d146d5c5de993"`, + `"0x5a3a9a67cc6b68eafd00fe3ffb1ea8755ef29cfa1d1c2f655efa61966ef248f7"`, ) }) test('different key types', () => { const auth_secp256k1 = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [{ token, limit: Value.from('10', 6) }], @@ -894,6 +918,7 @@ describe('getSignPayload', () => { const auth_p256 = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'p256', limits: [{ token, limit: Value.from('10', 6) }], @@ -901,6 +926,7 @@ describe('getSignPayload', () => { const auth_webauthn = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'webAuthn', limits: [{ token, limit: Value.from('10', 6) }], @@ -916,13 +942,13 @@ describe('getSignPayload', () => { expect(payload_p256).not.toBe(payload_webauthn) expect(payload_secp256k1).toMatchInlineSnapshot( - `"0x6d56b62bb5e94ca0206340e4bc1ece5a35e7ad31c30c6b98074d146d5c5de993"`, + `"0x5a3a9a67cc6b68eafd00fe3ffb1ea8755ef29cfa1d1c2f655efa61966ef248f7"`, ) expect(payload_p256).toMatchInlineSnapshot( - `"0x70f1d02570bd5ec14701306f7c3fadf405911fcd585136d1d60f2e4eb689f602"`, + `"0x6807f3a5597cdc334568094bb2a884fd97cfa21ecb7a8034e7fabef32680e5fe"`, ) expect(payload_webauthn).toMatchInlineSnapshot( - `"0xb69543e8899a3d0a0186e347df6589f47ea194b6bc3ac935225411b1ef2d4627"`, + `"0x576f0b2608ac4630b7f40f2b8571828e2e1e6f13ad9aca8c7ce53773407e9ce5"`, ) }) }) @@ -931,6 +957,7 @@ describe('deserialize', () => { test('default', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -951,6 +978,7 @@ describe('deserialize', () => { const authorization = KeyAuthorization.from( { address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -972,6 +1000,7 @@ describe('deserialize', () => { test('no limits', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', }) @@ -985,6 +1014,7 @@ describe('deserialize', () => { test('no expiry', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, type: 'secp256k1', }) @@ -999,6 +1029,7 @@ describe('hash', () => { test('default', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -1012,7 +1043,7 @@ describe('hash', () => { const hash = KeyAuthorization.hash(authorization) expect(hash).toMatchInlineSnapshot( - `"0x6d56b62bb5e94ca0206340e4bc1ece5a35e7ad31c30c6b98074d146d5c5de993"`, + `"0x5a3a9a67cc6b68eafd00fe3ffb1ea8755ef29cfa1d1c2f655efa61966ef248f7"`, ) }) }) @@ -1021,6 +1052,7 @@ describe('serialize', () => { test('default', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -1034,7 +1066,7 @@ describe('serialize', () => { const serialized = KeyAuthorization.serialize(authorization) expect(serialized).toMatchInlineSnapshot( - `"0xf838f7808094be95c3f554e9fc85ec51be69a3d807a0d55bcf2c84499602d2dad99420c000000000000000000000000000000000000183989680"`, + `"0xf838f7018094be95c3f554e9fc85ec51be69a3d807a0d55bcf2c84499602d2dad99420c000000000000000000000000000000000000183989680"`, ) }) @@ -1042,6 +1074,7 @@ describe('serialize', () => { const authorization = KeyAuthorization.from( { address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -1063,6 +1096,7 @@ describe('serialize', () => { test('no limits', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', }) @@ -1070,20 +1104,21 @@ describe('serialize', () => { const serialized = KeyAuthorization.serialize(authorization) expect(serialized).toMatchInlineSnapshot( - `"0xdddc808094be95c3f554e9fc85ec51be69a3d807a0d55bcf2c84499602d2"`, + `"0xdddc018094be95c3f554e9fc85ec51be69a3d807a0d55bcf2c84499602d2"`, ) }) test('no expiry', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, type: 'secp256k1', }) const serialized = KeyAuthorization.serialize(authorization) expect(serialized).toMatchInlineSnapshot( - `"0xd8d7808094be95c3f554e9fc85ec51be69a3d807a0d55bcf2c"`, + `"0xd8d7018094be95c3f554e9fc85ec51be69a3d807a0d55bcf2c"`, ) }) }) @@ -1092,6 +1127,7 @@ describe('toRpc', () => { test('secp256k1', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -1111,7 +1147,7 @@ describe('toRpc', () => { expect(rpc).toMatchInlineSnapshot(` { - "chainId": "0x", + "chainId": "0x1", "expiry": "0x499602d2", "keyId": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", "keyType": "secp256k1", @@ -1134,6 +1170,7 @@ describe('toRpc', () => { test('p256', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'p256', limits: [ @@ -1149,7 +1186,7 @@ describe('toRpc', () => { expect(rpc).toMatchInlineSnapshot(` { - "chainId": "0x", + "chainId": "0x1", "expiry": "0x499602d2", "keyId": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", "keyType": "p256", @@ -1174,6 +1211,7 @@ describe('toRpc', () => { test('webAuthn', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'webAuthn', limits: [ @@ -1189,7 +1227,7 @@ describe('toRpc', () => { expect(rpc).toMatchInlineSnapshot(` { - "chainId": "0x", + "chainId": "0x1", "expiry": "0x499602d2", "keyId": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", "keyType": "webAuthn", @@ -1214,6 +1252,7 @@ describe('toRpc', () => { test('multiple limits', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -1233,7 +1272,7 @@ describe('toRpc', () => { expect(rpc).toMatchInlineSnapshot(` { - "chainId": "0x", + "chainId": "0x1", "expiry": "0x499602d2", "keyId": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", "keyType": "secp256k1", @@ -1322,6 +1361,7 @@ describe('toTuple', () => { test('default', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, type: 'secp256k1', }) @@ -1330,7 +1370,7 @@ describe('toTuple', () => { expect(tuple).toMatchInlineSnapshot(` [ [ - "0x", + "0x1", "0x", "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", ], @@ -1341,6 +1381,7 @@ describe('toTuple', () => { test('with signature (secp256k1)', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -1357,7 +1398,7 @@ describe('toTuple', () => { expect(tuple).toMatchInlineSnapshot(` [ [ - "0x", + "0x1", "0x", "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", "0x499602d2", @@ -1376,6 +1417,7 @@ describe('toTuple', () => { test('with signature (p256)', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'p256', limits: [ @@ -1392,7 +1434,7 @@ describe('toTuple', () => { expect(tuple).toMatchInlineSnapshot(` [ [ - "0x", + "0x1", "0x01", "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", "0x499602d2", @@ -1411,6 +1453,7 @@ describe('toTuple', () => { test('with signature (webAuthn)', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'webAuthn', limits: [ @@ -1427,7 +1470,7 @@ describe('toTuple', () => { expect(tuple).toMatchInlineSnapshot(` [ [ - "0x", + "0x1", "0x02", "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", "0x499602d2", @@ -1446,6 +1489,7 @@ describe('toTuple', () => { test('multiple limits', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ @@ -1465,7 +1509,7 @@ describe('toTuple', () => { expect(tuple).toMatchInlineSnapshot(` [ [ - "0x", + "0x1", "0x", "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", "0x499602d2", @@ -1521,6 +1565,7 @@ describe('toTuple', () => { test('zero spending limit roundtrips through RLP', () => { const authorization = KeyAuthorization.from({ address, + chainId: 0n, expiry, type: 'secp256k1', limits: [ @@ -1562,6 +1607,7 @@ describe('toTuple', () => { test('zero expiry roundtrips through RLP', () => { const authorization = KeyAuthorization.from({ address, + chainId: 0n, expiry: 0, type: 'secp256k1', limits: [ @@ -1603,6 +1649,7 @@ describe('toTuple', () => { test('hash works with zero spending limit', () => { const authorization = KeyAuthorization.from({ address, + chainId: 1n, expiry, type: 'secp256k1', limits: [ diff --git a/src/tempo/KeyAuthorization.ts b/src/tempo/KeyAuthorization.ts index 6e602dff..820219ce 100644 --- a/src/tempo/KeyAuthorization.ts +++ b/src/tempo/KeyAuthorization.ts @@ -34,8 +34,8 @@ export type KeyAuthorization< > = { /** Address derived from the public key of the key type. */ address: Address.Address - /** Chain ID for replay protection (0 = valid on any chain). */ - chainId?: bigintType | undefined + /** Chain ID for replay protection. */ + chainId: bigintType /** Unix timestamp when key expires (0 = never expires). */ expiry?: numberType | null | undefined /** TIP20 spending limits for this key. */ @@ -136,6 +136,7 @@ export type TokenLimit = { * * const authorization = KeyAuthorization.from({ * address, + * chainId: 4217n, * expiry: 1234567890, * type: 'secp256k1', * limits: [{ @@ -157,6 +158,7 @@ export type TokenLimit = { * * const authorization = KeyAuthorization.from({ * address, + * chainId: 4217n, * expiry: 1234567890, * type: 'p256', * limits: [{ @@ -181,6 +183,7 @@ export type TokenLimit = { * * const authorization = KeyAuthorization.from({ * address, + * chainId: 4217n, * expiry: 1234567890, * type: 'secp256k1', * limits: [{ @@ -214,6 +217,7 @@ export type TokenLimit = { * * const authorization = KeyAuthorization.from({ * address, + * chainId: 4217n, * expiry: 1234567890, * type: 'p256', * limits: [{ @@ -313,7 +317,7 @@ export declare namespace from { * @returns A signed {@link ox#AuthorizationTempo.AuthorizationTempo}. */ export function fromRpc(authorization: Rpc): Signed { - const { chainId = '0x0', keyId, expiry = 0, limits, keyType } = authorization + const { chainId, keyId, expiry = 0, limits, keyType } = authorization const signature = SignatureEnvelope.fromRpc(authorization.signature) return { address: keyId, @@ -393,7 +397,7 @@ export function fromTuple( address: keyId, expiry: typeof expiry !== 'undefined' ? hexToNumber(expiry) : undefined, type: keyType, - ...(chainId !== '0x' ? { chainId: Hex.toBigInt(chainId) } : {}), + chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId), ...(typeof expiry !== 'undefined' ? { expiry: hexToNumber(expiry) } : {}), ...(typeof limits !== 'undefined' ? { @@ -436,6 +440,7 @@ export declare namespace fromTuple { * * const authorization = KeyAuthorization.from({ * address, + * chainId: 4217n, * expiry: 1234567890, * type: 'secp256k1', * limits: [{ @@ -467,8 +472,9 @@ export declare namespace getSignPayload { * import { Value } from 'ox' * * const authorization = KeyAuthorization.from({ - * expiry: 1234567890, * address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c', + * chainId: 4217n, + * expiry: 1234567890, * type: 'secp256k1', * limits: [{ * token: '0x20c0000000000000000000000000000000000001', @@ -504,8 +510,9 @@ export declare namespace deserialize { * import { Value } from 'ox' * * const authorization = KeyAuthorization.from({ - * expiry: 1234567890, * address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c', + * chainId: 4217n, + * expiry: 1234567890, * type: 'secp256k1', * limits: [{ * token: '0x20c0000000000000000000000000000000000001', @@ -543,8 +550,9 @@ export declare namespace hash { * import { Value } from 'ox' * * const authorization = KeyAuthorization.from({ - * expiry: 1234567890, * address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c', + * chainId: 4217n, + * expiry: 1234567890, * type: 'secp256k1', * limits: [{ * token: '0x20c0000000000000000000000000000000000001', @@ -579,8 +587,9 @@ export declare namespace serialize { * import { Value } from 'ox' * * const authorization = KeyAuthorization.toRpc({ - * expiry: 1234567890, * address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c', + * chainId: 4217n, + * expiry: 1234567890, * type: 'secp256k1', * limits: [{ * token: '0x20c0000000000000000000000000000000000001', @@ -601,14 +610,7 @@ export declare namespace serialize { * @returns An RPC-formatted Key Authorization. */ export function toRpc(authorization: Signed): Rpc { - const { - address, - chainId = 0n, - expiry, - limits, - type, - signature, - } = authorization + const { address, chainId, expiry, limits, type, signature } = authorization return { chainId: chainId === 0n ? '0x' : Hex.fromNumber(chainId), @@ -636,8 +638,9 @@ export declare namespace toRpc { * import { Value } from 'ox' * * const authorization = KeyAuthorization.from({ - * expiry: 1234567890, * address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c', + * chainId: 4217n, + * expiry: 1234567890, * type: 'secp256k1', * limits: [{ * token: '0x20c0000000000000000000000000000000000001', @@ -660,7 +663,7 @@ export declare namespace toRpc { export function toTuple( authorization: authorization, ): toTuple.ReturnType { - const { address, chainId = 0n, expiry, limits } = authorization + const { address, chainId, expiry, limits } = authorization const signature = authorization.signature ? SignatureEnvelope.serialize(authorization.signature) : undefined diff --git a/src/tempo/SignatureEnvelope.test.ts b/src/tempo/SignatureEnvelope.test.ts index 65aeb48b..5bd14d7c 100644 --- a/src/tempo/SignatureEnvelope.test.ts +++ b/src/tempo/SignatureEnvelope.test.ts @@ -50,16 +50,19 @@ const signature_webauthn = SignatureEnvelope.from({ const signature_keychain_secp256k1 = SignatureEnvelope.from({ userAddress: '0x1234567890123456789012345678901234567890', inner: SignatureEnvelope.from(signature_secp256k1), + version: 'v2', }) const signature_keychain_p256 = SignatureEnvelope.from({ userAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd', inner: signature_p256, + version: 'v2', }) const signature_keychain_webauthn = SignatureEnvelope.from({ userAddress: '0xfedcbafedcbafedcbafedcbafedcbafedcbafedc', inner: signature_webauthn, + version: 'v2', }) describe('assert', () => { @@ -509,7 +512,7 @@ describe('deserialize', () => { SignatureEnvelope.deserialize('0xdeadbeef'), ).toThrowErrorMatchingInlineSnapshot( ` - [SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: Unknown signature type identifier: 0xde. Expected 0x01 (P256) or 0x02 (WebAuthn) + [SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: Unknown signature type identifier: 0xde. Expected 0x01 (P256), 0x02 (WebAuthn), 0x03 (Keychain V1), or 0x04 (Keychain V2) Serialized: 0xdeadbeef] `, @@ -669,7 +672,7 @@ describe('deserialize', () => { SignatureEnvelope.deserialize(unknownType), ).toThrowErrorMatchingInlineSnapshot( ` - [SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: Unknown signature type identifier: 0xff. Expected 0x01 (P256) or 0x02 (WebAuthn) + [SignatureEnvelope.InvalidSerializedError: Unable to deserialize signature envelope: Unknown signature type identifier: 0xff. Expected 0x01 (P256), 0x02 (WebAuthn), 0x03 (Keychain V1), or 0x04 (Keychain V2) Serialized: 0xff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000] `, @@ -712,6 +715,7 @@ describe('deserialize', () => { }, "type": "keychain", "userAddress": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "version": "v2", } `) }) @@ -742,6 +746,7 @@ describe('deserialize', () => { }, "type": "keychain", "userAddress": "0xfedcbafedcbafedcbafedcbafedcbafedcbafedc", + "version": "v2", } `) }) @@ -1250,8 +1255,8 @@ describe('serialize', () => { // Should be: 1 (type) + 20 (address) + 65 (secp256k1 signature) expect(Hex.size(serialized)).toBe(1 + 20 + 65) - // First byte should be Keychain type identifier (0x03) - expect(Hex.slice(serialized, 0, 1)).toBe('0x03') + // First byte should be Keychain V2 type identifier (0x04) + expect(Hex.slice(serialized, 0, 1)).toBe('0x04') // Next 20 bytes should be the user address (without '0x') expect(Hex.slice(serialized, 1, 21)).toBe( @@ -1265,8 +1270,8 @@ describe('serialize', () => { // Should be: 1 (type) + 20 (address) + 130 (p256 signature with type) expect(Hex.size(serialized)).toBe(1 + 20 + 130) - // First byte should be Keychain type identifier (0x03) - expect(Hex.slice(serialized, 0, 1)).toBe('0x03') + // First byte should be Keychain V2 type identifier (0x04) + expect(Hex.slice(serialized, 0, 1)).toBe('0x04') // Next 20 bytes should be the user address (without '0x') expect(Hex.slice(serialized, 1, 21)).toBe( @@ -1284,8 +1289,8 @@ describe('serialize', () => { signature_keychain_webauthn, ) - // First byte should be Keychain type identifier (0x03) - expect(Hex.slice(serialized, 0, 1)).toBe('0x03') + // First byte should be Keychain V2 type identifier (0x04) + expect(Hex.slice(serialized, 0, 1)).toBe('0x04') // Should contain the user address expect(Hex.slice(serialized, 1, 21)).toBe( @@ -1519,6 +1524,7 @@ describe('serialize', () => { }, "type": "keychain", "userAddress": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "version": "v2", } `) }) @@ -1549,6 +1555,7 @@ describe('serialize', () => { }, "type": "keychain", "userAddress": "0xfedcbafedcbafedcbafedcbafedcbafedcbafedc", + "version": "v2", } `) }) diff --git a/src/tempo/SignatureEnvelope.ts b/src/tempo/SignatureEnvelope.ts index 68b99ae8..34c7f9ea 100644 --- a/src/tempo/SignatureEnvelope.ts +++ b/src/tempo/SignatureEnvelope.ts @@ -22,6 +22,7 @@ import * as ox_WebAuthnP256 from '../core/WebAuthnP256.js' const serializedP256Type = '0x01' const serializedWebAuthnType = '0x02' const serializedKeychainType = '0x03' +const serializedKeychainV2Type = '0x04' /** Serialized magic identifier for Tempo signature envelopes. */ export const magicBytes = @@ -86,9 +87,10 @@ export type GetType< * and clientDataJSON. Enables browser passkey authentication. The signature is also * charged as calldata (16 gas/non-zero byte, 4 gas/zero byte). * - * - **keychain** (type `0x03`): Access key signature that wraps an inner signature (secp256k1, - * p256, or webAuthn). Format: `0x03` + user_address (20 bytes) + inner signature. The - * protocol validates the access key authorization via the AccountKeychain precompile. + * - **keychain** (type `0x03` V1, `0x04` V2): Access key signature that wraps an inner signature + * (secp256k1, p256, or webAuthn). Format: type byte + user_address (20 bytes) + inner signature. + * V2 binds the signature to the user account via `keccak256(sigHash || userAddress)`. + * The protocol validates the access key authorization via the AccountKeychain precompile. * * [Signature Types Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types) */ @@ -106,18 +108,30 @@ export type SignatureEnvelopeRpc = OneOf< Secp256k1Rpc | P256Rpc | WebAuthnRpc | KeychainRpc > +/** + * Keychain signature version. + * + * - `'v1'`: Legacy format. Inner signature signs the raw `sig_hash` directly. Deprecated at T1C. + * - `'v2'`: Inner signature signs `keccak256(sig_hash || user_address)`, binding the signature + * to the specific user account. + */ +export type KeychainVersion = 'v1' | 'v2' + export type Keychain = { /** Root account address that this transaction is being executed for */ userAddress: Address.Address /** The actual signature from the access key (can be Secp256k1, P256, or WebAuthn) */ inner: SignatureEnvelope type: 'keychain' + /** Keychain signature version. @default 'v1' */ + version?: KeychainVersion | undefined } export type KeychainRpc = { type: 'keychain' userAddress: Address.Address signature: SignatureEnvelopeRpc + version?: KeychainVersion | undefined } export type P256 = { @@ -389,7 +403,8 @@ export declare namespace extractPublicKey { * - 65 bytes (no prefix): secp256k1 signature * - Type `0x01` + 129 bytes: P256 signature (r, s, pubKeyX, pubKeyY, prehash) * - Type `0x02` + variable: WebAuthn signature (webauthnData, r, s, pubKeyX, pubKeyY) - * - Type `0x03` + 20 bytes + inner: Keychain signature (userAddress + inner signature) + * - Type `0x03` + 20 bytes + inner: Keychain V1 signature (userAddress + inner signature) + * - Type `0x04` + 20 bytes + inner: Keychain V2 signature (userAddress + inner signature) * * [Signature Types](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types) * @@ -510,7 +525,10 @@ export function deserialize(value: Serialized): SignatureEnvelope { } satisfies WebAuthn } - if (typeId === serializedKeychainType) { + if ( + typeId === serializedKeychainType || + typeId === serializedKeychainV2Type + ) { const userAddress = Hex.slice(data, 0, 20) const inner = deserialize(Hex.slice(data, 20)) @@ -518,11 +536,12 @@ export function deserialize(value: Serialized): SignatureEnvelope { userAddress, inner, type: 'keychain', + version: typeId === serializedKeychainV2Type ? 'v2' : 'v1', } satisfies Keychain } throw new InvalidSerializedError({ - reason: `Unknown signature type identifier: ${typeId}. Expected ${serializedP256Type} (P256) or ${serializedWebAuthnType} (WebAuthn)`, + reason: `Unknown signature type identifier: ${typeId}. Expected ${serializedP256Type} (P256), ${serializedWebAuthnType} (WebAuthn), ${serializedKeychainType} (Keychain V1), or ${serializedKeychainV2Type} (Keychain V2)`, serialized, }) } @@ -660,6 +679,15 @@ export function from( return { ...value, ...(type === 'p256' ? { prehash: value.prehash } : {}), + ...(type === 'keychain' && + !( + typeof value === 'object' && + value !== null && + 'version' in value && + value.version + ) + ? { version: 'v1' } + : {}), type, } as never } @@ -778,6 +806,7 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope { type: 'keychain', userAddress: envelope.userAddress, inner: fromRpc(envelope.signature), + ...(envelope.version ? { version: envelope.version } : {}), } throw new CoercionError({ envelope }) @@ -868,7 +897,8 @@ export function getType< * - secp256k1: 65 bytes (no type prefix, for backward compatibility) * - P256: `0x01` + r (32) + s (32) + pubKeyX (32) + pubKeyY (32) + prehash (1) = 130 bytes * - WebAuthn: `0x02` + webauthnData (variable) + r (32) + s (32) + pubKeyX (32) + pubKeyY (32) - * - Keychain: `0x03` + userAddress (20) + inner signature (recursive) + * - Keychain V1: `0x03` + userAddress (20) + inner signature (recursive) + * - Keychain V2: `0x04` + userAddress (20) + inner signature (recursive) * * [Signature Types](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types) * @@ -936,8 +966,12 @@ export function serialize( if (type === 'keychain') { const keychain = envelope as Keychain + const keychainTypeId = + keychain.version === 'v1' + ? serializedKeychainType + : serializedKeychainV2Type return Hex.concat( - serializedKeychainType, + keychainTypeId, keychain.userAddress, serialize(keychain.inner), options.magic ? magicBytes : '0x', @@ -1019,6 +1053,7 @@ export function toRpc(envelope: SignatureEnvelope): SignatureEnvelopeRpc { type: 'keychain', userAddress: keychain.userAddress, signature: toRpc(keychain.inner), + ...(keychain.version ? { version: keychain.version } : {}), } } diff --git a/src/tempo/Transaction.test.ts b/src/tempo/Transaction.test.ts index 286b8f3c..4923f070 100644 --- a/src/tempo/Transaction.test.ts +++ b/src/tempo/Transaction.test.ts @@ -164,6 +164,7 @@ describe('fromRpc', () => { }, ], keyAuthorization: { + chainId: '0x1', expiry: '0xffffffffffff', keyId: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c', keyType: 'secp256k1', @@ -224,7 +225,7 @@ describe('fromRpc', () => { "hash": "0x353fdfc38a2f26115daadee9f5b8392ce62b84f410957967e2ed56b35338cdd0", "keyAuthorization": { "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", - "chainId": 0n, + "chainId": 1n, "expiry": 281474976710655, "limits": [ { @@ -431,6 +432,7 @@ describe('toRpc', () => { data: undefined, keyAuthorization: { address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c', + chainId: 1n, expiry: 281474976710655, type: 'secp256k1', limits: [ @@ -487,7 +489,7 @@ describe('toRpc', () => { "hash": "0x353fdfc38a2f26115daadee9f5b8392ce62b84f410957967e2ed56b35338cdd0", "input": undefined, "keyAuthorization": { - "chainId": "0x", + "chainId": "0x1", "expiry": "0xffffffffffff", "keyId": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c", "keyType": "secp256k1", diff --git a/src/tempo/TxEnvelopeTempo.test.ts b/src/tempo/TxEnvelopeTempo.test.ts index e5a983f3..136add92 100644 --- a/src/tempo/TxEnvelopeTempo.test.ts +++ b/src/tempo/TxEnvelopeTempo.test.ts @@ -257,6 +257,7 @@ describe('deserialize', () => { const keyAuthorization = KeyAuthorization.from({ expiry: 1234567890, address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c', + chainId: 1n, type: 'secp256k1', limits: [ { @@ -894,6 +895,7 @@ describe('serialize', () => { test('keyAuthorization (secp256k1)', () => { const keyAuthorization = KeyAuthorization.from({ address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c', + chainId: 1n, expiry: 1234567890, type: 'secp256k1', limits: [ @@ -918,7 +920,7 @@ describe('serialize', () => { const serialized = TxEnvelopeTempo.serialize(transaction) expect(serialized).toMatchInlineSnapshot( - `"0x76f8a201808080d8d79470997970c51812dc3a010c7d01b50e0d17dc79c88080c0808080808080c0f87bf7808094be95c3f554e9fc85ec51be69a3d807a0d55bcf2c84499602d2dad99420c000000000000000000000000000000000000183989680b841635dc2033e60185bb36709c29c75d64ea51dfbd91c32ef4be198e4ceb169fb4d50c2667ac4c771072746acfdcf1f1483336dcca8bd2df47cd83175dbe60f05401b"`, + `"0x76f8a201808080d8d79470997970c51812dc3a010c7d01b50e0d17dc79c88080c0808080808080c0f87bf7018094be95c3f554e9fc85ec51be69a3d807a0d55bcf2c84499602d2dad99420c000000000000000000000000000000000000183989680b841635dc2033e60185bb36709c29c75d64ea51dfbd91c32ef4be198e4ceb169fb4d50c2667ac4c771072746acfdcf1f1483336dcca8bd2df47cd83175dbe60f05401b"`, ) const deserialized = TxEnvelopeTempo.deserialize(serialized) @@ -928,6 +930,7 @@ describe('serialize', () => { test('keyAuthorization (p256)', () => { const keyAuthorization = KeyAuthorization.from({ address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c', + chainId: 1n, expiry: 1234567890, type: 'p256', limits: [ @@ -959,7 +962,7 @@ describe('serialize', () => { const serialized = TxEnvelopeTempo.serialize(transaction) expect(serialized).toMatchInlineSnapshot( - `"0x76f8e301808080d8d79470997970c51812dc3a010c7d01b50e0d17dc79c88080c0808080808080c0f8bcf7800194be95c3f554e9fc85ec51be69a3d807a0d55bcf2c84499602d2dad99420c000000000000000000000000000000000000183989680b88201ccbb3485d4726235f13cb15ef394fb7158179fb7b1925eccec0147671090c52e77c3c53373cc1e3b05e7c23f609deb17cea8fe097300c45411237e9fe4166b35ad8ac16e167d6992c3e120d7f17d2376bc1cbcf30c46ba6dd00ce07303e742f511edf6ce1c32de66846f56afa7be1cbd729bc35750b6d0cdcf3ec9d75461aba001"`, + `"0x76f8e301808080d8d79470997970c51812dc3a010c7d01b50e0d17dc79c88080c0808080808080c0f8bcf7010194be95c3f554e9fc85ec51be69a3d807a0d55bcf2c84499602d2dad99420c000000000000000000000000000000000000183989680b88201ccbb3485d4726235f13cb15ef394fb7158179fb7b1925eccec0147671090c52e77c3c53373cc1e3b05e7c23f609deb17cea8fe097300c45411237e9fe4166b35ad8ac16e167d6992c3e120d7f17d2376bc1cbcf30c46ba6dd00ce07303e742f511edf6ce1c32de66846f56afa7be1cbd729bc35750b6d0cdcf3ec9d75461aba001"`, ) const deserialized = TxEnvelopeTempo.deserialize(serialized) @@ -979,6 +982,7 @@ describe('serialize', () => { const keyAuthorization = KeyAuthorization.from({ address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c', + chainId: 1n, expiry: 1234567890, type: 'webAuthn', limits: [ @@ -1010,7 +1014,7 @@ describe('serialize', () => { const serialized = TxEnvelopeTempo.serialize(transaction) expect(serialized).toMatchInlineSnapshot( - `"0x76f9016501808080d8d79470997970c51812dc3a010c7d01b50e0d17dc79c88080c0808080808080c0f9013df7800294be95c3f554e9fc85ec51be69a3d807a0d55bcf2c84499602d2dad99420c000000000000000000000000000000000000183989680b901020249960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976305000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a223371322d3777222c226f726967696e223a22687474703a2f2f6c6f63616c686f7374222c2263726f73734f726967696e223a66616c73657dccbb3485d4726235f13cb15ef394fb7158179fb7b1925eccec0147671090c52e77c3c53373cc1e3b05e7c23f609deb17cea8fe097300c45411237e9fe4166b35ad8ac16e167d6992c3e120d7f17d2376bc1cbcf30c46ba6dd00ce07303e742f511edf6ce1c32de66846f56afa7be1cbd729bc35750b6d0cdcf3ec9d75461aba0"`, + `"0x76f9016501808080d8d79470997970c51812dc3a010c7d01b50e0d17dc79c88080c0808080808080c0f9013df7010294be95c3f554e9fc85ec51be69a3d807a0d55bcf2c84499602d2dad99420c000000000000000000000000000000000000183989680b901020249960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976305000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a223371322d3777222c226f726967696e223a22687474703a2f2f6c6f63616c686f7374222c2263726f73734f726967696e223a66616c73657dccbb3485d4726235f13cb15ef394fb7158179fb7b1925eccec0147671090c52e77c3c53373cc1e3b05e7c23f609deb17cea8fe097300c45411237e9fe4166b35ad8ac16e167d6992c3e120d7f17d2376bc1cbcf30c46ba6dd00ce07303e742f511edf6ce1c32de66846f56afa7be1cbd729bc35750b6d0cdcf3ec9d75461aba0"`, ) // Verify roundtrip diff --git a/src/tempo/TxEnvelopeTempo.ts b/src/tempo/TxEnvelopeTempo.ts index 3ce937ee..452d798e 100644 --- a/src/tempo/TxEnvelopeTempo.ts +++ b/src/tempo/TxEnvelopeTempo.ts @@ -779,16 +779,67 @@ export declare namespace serialize { * const signature = Secp256k1.sign({ payload, privateKey: '0x...' }) * ``` * + * @example + * ### Access Keys + * + * When signing as an access key on behalf of a root account, pass the + * `from` option with the root account address. This computes + * `keccak256(0x04 || sigHash || from)` which binds the signature to the + * specific user account (V2 keychain format). + * + * ```ts twoslash + * // @noErrors + * import { Secp256k1 } from 'ox' + * import { TxEnvelopeTempo, SignatureEnvelope } from 'ox/tempo' + * + * const envelope = TxEnvelopeTempo.from({ + * chainId: 1, + * calls: [{ + * data: '0xdeadbeef', + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * }], + * nonce: 0n, + * maxFeePerGas: 1000000000n, + * gas: 21000n, + * }) + * + * const payload = TxEnvelopeTempo.getSignPayload(envelope, { from: '0x...' }) // [!code focus] + * + * const signature = Secp256k1.sign({ payload, privateKey: '0x...' }) + * + * const signed = TxEnvelopeTempo.serialize(envelope, { + * signature: SignatureEnvelope.from({ + * userAddress: from, + * inner: SignatureEnvelope.from(signature), + * }), + * }) + * ``` + * * @param envelope - The transaction envelope to get the sign payload for. + * @param options - Options. * @returns The sign payload. */ export function getSignPayload( envelope: TxEnvelopeTempo, + options: getSignPayload.Options = {}, ): getSignPayload.ReturnValue { - return hash(envelope, { presign: true }) + const sigHash = hash(envelope, { presign: true }) + if (options.from) + return Hash.keccak256(Hex.concat('0x04', sigHash, options.from)) + return sigHash } export declare namespace getSignPayload { + type Options = { + /** + * The root account address for access key signing. + * + * When provided, computes `keccak256(0x04 || sigHash || from)` instead of + * the raw `sigHash`, binding the access key signature to the specific user account. + */ + from?: Address.Address | undefined + } + type ReturnValue = Hex.Hex type ErrorType = hash.ErrorType | Errors.GlobalErrorType diff --git a/src/tempo/e2e.test.ts b/src/tempo/e2e.test.ts index fbda4552..09dd7fd7 100644 --- a/src/tempo/e2e.test.ts +++ b/src/tempo/e2e.test.ts @@ -9,7 +9,7 @@ import { } from 'ox' import { getTransactionCount } from 'viem/actions' import { beforeEach, describe, expect, test } from 'vitest' -import { chain, client, fundAddress } from '../../test/tempo/config.js' +import { chain, client, fundAddress, nodeEnv } from '../../test/tempo/config.js' import { AuthorizationTempo, KeyAuthorization, @@ -20,6 +20,8 @@ import * as TransactionReceipt from './TransactionReceipt.js' import * as TxEnvelopeTempo from './TxEnvelopeTempo.js' const chainId = chain.id +// TODO: remove when v2 keychain signatures are deployed to testnet. +const keychainVersion = nodeEnv === 'localnet' ? 'v2' : undefined test('behavior: default (secp256k1)', async () => { const privateKey = Secp256k1.randomPrivateKey() @@ -806,6 +808,7 @@ describe('behavior: keyAuthorization', () => { const keyAuth = KeyAuthorization.from({ address: access.address, + chainId: BigInt(chain.id), type: 'secp256k1', }) @@ -839,7 +842,9 @@ describe('behavior: keyAuthorization', () => { }) const signature = Secp256k1.sign({ - payload: TxEnvelopeTempo.getSignPayload(transaction), + payload: TxEnvelopeTempo.getSignPayload(transaction, { + from: root.address, + }), privateKey: access.privateKey, }) @@ -848,6 +853,7 @@ describe('behavior: keyAuthorization', () => { userAddress: root.address, inner: SignatureEnvelope.from(signature), type: 'keychain', + version: keychainVersion, }), }) @@ -978,7 +984,9 @@ describe('behavior: keyAuthorization', () => { }) const signature = Secp256k1.sign({ - payload: TxEnvelopeTempo.getSignPayload(transaction), + payload: TxEnvelopeTempo.getSignPayload(transaction, { + from: root.address, + }), privateKey: access.privateKey, }) @@ -987,6 +995,7 @@ describe('behavior: keyAuthorization', () => { userAddress: root.address, inner: SignatureEnvelope.from(signature), type: 'keychain', + version: keychainVersion, }), }) @@ -1012,6 +1021,7 @@ describe('behavior: keyAuthorization', () => { const keyAuth = KeyAuthorization.from({ address: access.address, + chainId: BigInt(chainId), type: 'p256', }) @@ -1045,7 +1055,9 @@ describe('behavior: keyAuthorization', () => { }) const signature = P256.sign({ - payload: TxEnvelopeTempo.getSignPayload(transaction), + payload: TxEnvelopeTempo.getSignPayload(transaction, { + from: root.address, + }), privateKey: access.privateKey, }) @@ -1059,6 +1071,7 @@ describe('behavior: keyAuthorization', () => { type: 'p256', }), type: 'keychain', + version: keychainVersion, }), }) @@ -1190,7 +1203,9 @@ describe('behavior: keyAuthorization', () => { }) const signature = P256.sign({ - payload: TxEnvelopeTempo.getSignPayload(transaction), + payload: TxEnvelopeTempo.getSignPayload(transaction, { + from: root.address, + }), privateKey: access.privateKey, }) @@ -1204,6 +1219,7 @@ describe('behavior: keyAuthorization', () => { type: 'p256', }), type: 'keychain', + version: keychainVersion, }), }) @@ -1227,6 +1243,7 @@ describe('behavior: keyAuthorization', () => { const keyAuth = KeyAuthorization.from({ address: access.address, + chainId: BigInt(chainId), type: 'p256', }) @@ -1260,7 +1277,9 @@ describe('behavior: keyAuthorization', () => { }) const signature = await WebCryptoP256.sign({ - payload: TxEnvelopeTempo.getSignPayload(transaction), + payload: TxEnvelopeTempo.getSignPayload(transaction, { + from: root.address, + }), privateKey: keyPair.privateKey, }) @@ -1274,6 +1293,7 @@ describe('behavior: keyAuthorization', () => { type: 'p256', }), type: 'keychain', + version: keychainVersion, }), }) @@ -1366,7 +1386,9 @@ describe('behavior: keyAuthorization', () => { }) const signature = await WebCryptoP256.sign({ - payload: TxEnvelopeTempo.getSignPayload(transaction), + payload: TxEnvelopeTempo.getSignPayload(transaction, { + from: root.address, + }), privateKey: keyPair.privateKey, }) @@ -1380,6 +1402,7 @@ describe('behavior: keyAuthorization', () => { type: 'p256', }), type: 'keychain', + version: keychainVersion, }), }) @@ -1404,6 +1427,7 @@ describe('behavior: keyAuthorization', () => { const keyAuth = KeyAuthorization.from({ address: access.address, + chainId: BigInt(chainId), type: 'p256', expiry: Math.floor(Date.now() / 1000) + 60 * 60, limits: [ @@ -1444,7 +1468,9 @@ describe('behavior: keyAuthorization', () => { }) const signature = P256.sign({ - payload: TxEnvelopeTempo.getSignPayload(transaction), + payload: TxEnvelopeTempo.getSignPayload(transaction, { + from: root.address, + }), privateKey: access.privateKey, }) @@ -1458,6 +1484,7 @@ describe('behavior: keyAuthorization', () => { type: 'p256', }), type: 'keychain', + version: keychainVersion, }), }) @@ -1498,6 +1525,7 @@ describe('behavior: keyAuthorization', () => { const keyAuth = KeyAuthorization.from({ address: access.address, + chainId: BigInt(chainId), type: 'secp256k1', limits: [ { @@ -1537,7 +1565,9 @@ describe('behavior: keyAuthorization', () => { }) const signature = Secp256k1.sign({ - payload: TxEnvelopeTempo.getSignPayload(transaction), + payload: TxEnvelopeTempo.getSignPayload(transaction, { + from: root.address, + }), privateKey: access.privateKey, }) @@ -1546,6 +1576,7 @@ describe('behavior: keyAuthorization', () => { userAddress: root.address, inner: SignatureEnvelope.from(signature), type: 'keychain', + version: keychainVersion, }), }) @@ -1586,6 +1617,7 @@ describe('behavior: keyAuthorization', () => { const keyAuth = KeyAuthorization.from({ address: access.address, + chainId: BigInt(chainId), type: 'secp256k1', expiry: Math.floor(Date.now() / 1000) + 60 * 60, }) @@ -1620,7 +1652,9 @@ describe('behavior: keyAuthorization', () => { }) const signature = Secp256k1.sign({ - payload: TxEnvelopeTempo.getSignPayload(transaction), + payload: TxEnvelopeTempo.getSignPayload(transaction, { + from: root.address, + }), privateKey: access.privateKey, }) @@ -1629,6 +1663,7 @@ describe('behavior: keyAuthorization', () => { userAddress: root.address, inner: SignatureEnvelope.from(signature), type: 'keychain', + version: keychainVersion, }), }) diff --git a/src/tempo/index.ts b/src/tempo/index.ts index 94386b5a..d74afbea 100644 --- a/src/tempo/index.ts +++ b/src/tempo/index.ts @@ -120,27 +120,7 @@ export * as PoolId from './PoolId.js' * @category Reference */ export * as SignatureEnvelope from './SignatureEnvelope.js' -/** - * Tempo address encoding/decoding utilities for human-readable addresses. - * - * Tempo addresses use a bech32 base32-encoded format with `tempo1` prefix for mainnet - * and `tempoz1` prefix for zone addresses. Includes CompactSize zone ID encoding - * and double-SHA256 checksumming. - * - * @example - * ```ts twoslash - * import { TempoAddress } from 'ox/tempo' - * - * const encoded = TempoAddress.format('0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28') - * // @log: 'tempo1wskntnrxxnq9x2f95wuyf0y7wk2l90fg8zd8djs' - * - * const { address, zoneId } = TempoAddress.parse(encoded) - * // @log: { address: '0x742d35CC6634c0532925a3B844bc9e7595F2Bd28' } - * ``` - * - * @category Reference - */ -export * as TempoAddress from './TempoAddress.js' + /** * Tick-based pricing utilities for DEX price conversions. * @@ -161,6 +141,7 @@ export * as TempoAddress from './TempoAddress.js' * @category Reference */ export * as Tick from './Tick.js' + /** * TIP-20 token ID utilities for converting between token IDs and addresses. * @@ -182,6 +163,7 @@ export * as Tick from './Tick.js' * @category Reference */ export * as TokenId from './TokenId.js' + /** * Token role utilities for serializing role identifiers to keccak256 hashes. *