Skip to content

Commit 93bf1b1

Browse files
authored
Prepare auth signing for Protocol 27 (#31)
Harden address-bound authorization payloads, upgrade to stable Stellar SDK 16.0.1, regenerate Protocol 27 SAC bindings, prepare coordinated package releases, and add CI.
1 parent 1b3aa63 commit 93bf1b1

19 files changed

Lines changed: 1098 additions & 1063 deletions

File tree

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- next
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
sdk:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: pnpm/action-setup@v4
18+
with:
19+
version: 10.30.3
20+
- uses: actions/setup-node@v4
21+
with:
22+
node-version: 22.18.0
23+
cache: pnpm
24+
- uses: oven-sh/setup-bun@v2
25+
26+
- run: pnpm install --frozen-lockfile
27+
- run: pnpm run build
28+
- run: pnpm exec tsc --noEmit --pretty false
29+
- run: pnpm run test:p27-auth
30+
- run: pnpm --dir demo install --frozen-lockfile
31+
- run: pnpm --dir demo run check
32+
- run: pnpm --dir demo run build
33+
- run: git diff --check

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ A TypeScript SDK for creating and managing Stellar smart wallets using passkeys.
2727
pnpm i passkey-kit
2828
```
2929

30+
Protocol 27 support requires Node.js 22.18 or newer and Stellar SDK 16.0.1 or newer:
31+
32+
```bash
33+
pnpm add passkey-kit@^0.13.0 @stellar/stellar-sdk@^16.0.1
34+
```
35+
36+
Passkey Kit signs legacy `ADDRESS` and Protocol 27 `ADDRESS_V2` authorization entries. It currently rejects `ADDRESS_WITH_DELEGATES` entries because delegate-tree signing is not implemented. Existing smart-wallet contract deployments do not need a WASM upgrade for this client-side signing change.
37+
3038
## Exports
3139

3240
```ts

bun_tests/5.index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Build an assembled transaction manually
22

33
import { PasskeyClient } from 'passkey-kit'
4-
import { AssembledTransaction } from '@stellar/stellar-sdk/minimal/contract';
4+
import { AssembledTransaction } from '@stellar/stellar-sdk/contract';
55

66
const tx_xdr = 'AAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACZ30AAAAAAAAAAQAAAAEAAAAAAAAAAAAAAABm+xLbAAAAAAAAAAEAAAAAAAAAGAAAAAAAAAAB15KLcsJwPM/q9+uf9O9NUEpVqLl5/JtFDqLIQrTRzmEAAAAIdHJhbnNmZXIAAAADAAAAEgAAAAGeKf+NLk3Jb5f2jCWLsZdKH31uJ94AOPposffyxd+2qAAAABIAAAABh/ZytVEXZeou5dLsuMEZdkkVCjaICnCTq0CmRionsIgAAAAKAAAAAAAAAAAAAAAAAJiWgAAAAAEAAAABAAAAAZ4p/40uTclvl/aMJYuxl0offW4n3gA4+mix9/LF37aoSOpiY5Ioi98AA0z8AAAAEAAAAAEAAAABAAAAEQAAAAEAAAABAAAAEAAAAAEAAAACAAAADwAAAAlTZWNwMjU2cjEAAAAAAAANAAAAFKrB2chB1uk2vswAZo5AakLnkqG/AAAAEAAAAAEAAAACAAAADwAAAAlTZWNwMjU2cjEAAAAAAAARAAAAAQAAAAMAAAAPAAAAEmF1dGhlbnRpY2F0b3JfZGF0YQAAAAAADQAAACXKupuc4WurWb3NCdWC5nW7akt6c3YA07IZIg7j6tAwSR0AAAAAAAAAAAAADwAAABBjbGllbnRfZGF0YV9qc29uAAAADQAAAJh7InR5cGUiOiJ3ZWJhdXRobi5nZXQiLCJjaGFsbGVuZ2UiOiJ5Zi1wZWlsRGlEN002cmhMZWZlMU8zQV80ZlE5STZOVGVIT0YwZ1ljVk00Iiwib3JpZ2luIjoiaHR0cHM6Ly9uZXh0LnBhc3NrZXkta2l0LWRlbW8ucGFnZXMuZGV2IiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQAAAA8AAAAJc2lnbmF0dXJlAAAAAAAADQAAAEAtCj4OlKr5N+YX6ny5jYu5XXmGEq9adAQr/GSm9nPOqhDoqWDRL0yD466GjttJhp235xorK0sLB5xgWeLON2VMAAAAAAAAAAHXkotywnA8z+r365/0701QSlWouXn8m0UOoshCtNHOYQAAAAh0cmFuc2ZlcgAAAAMAAAASAAAAAZ4p/40uTclvl/aMJYuxl0offW4n3gA4+mix9/LF37aoAAAAEgAAAAGH9nK1URdl6i7l0uy4wRl2SRUKNogKcJOrQKZGKiewiAAAAAoAAAAAAAAAAAAAAAAAmJaAAAAAAAAAAAEAAAAAAAAAAQAAAAYAAAAB15KLcsJwPM/q9+uf9O9NUEpVqLl5/JtFDqLIQrTRzmEAAAAUAAAAAQAAAAMAAAAGAAAAAZ4p/40uTclvl/aMJYuxl0offW4n3gA4+mix9/LF37aoAAAAFUjqYmOSKIvfAAAAAAAAAAYAAAAB15KLcsJwPM/q9+uf9O9NUEpVqLl5/JtFDqLIQrTRzmEAAAAQAAAAAQAAAAIAAAAPAAAAB0JhbGFuY2UAAAAAEgAAAAGH9nK1URdl6i7l0uy4wRl2SRUKNogKcJOrQKZGKiewiAAAAAEAAAAGAAAAAdeSi3LCcDzP6vfrn/TvTVBKVai5efybRQ6iyEK00c5hAAAAEAAAAAEAAAACAAAADwAAAAdCYWxhbmNlAAAAABIAAAABnin/jS5NyW+X9owli7GXSh99bifeADj6aLH38sXftqgAAAABAAWL7QAAArgAAAIIAAAAAAACZxkAAAAA'
77

bun_tests/7.index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Test an XDR that deploys a contract
22

3-
import { Networks, Operation, TransactionBuilder, xdr } from '@stellar/stellar-sdk/minimal';
4-
import { AssembledTransaction, type AssembledTransactionOptions, type Tx } from '@stellar/stellar-sdk/minimal/contract';
3+
import { Networks, Operation, TransactionBuilder, xdr } from '@stellar/stellar-sdk';
4+
import { AssembledTransaction, type AssembledTransactionOptions, type Tx } from '@stellar/stellar-sdk/contract';
55
import { Client as PasskeyClient } from 'passkey-kit-sdk';
66

77
let xdr_base64 = 'AAAAAgAAAAC4YTsgfBnWL+VK8itDD/gCnqtirv5cWHO3MXxqONNVmgAAAGQAESPAAAAAAQAAAAEAAAAAAAAAAAAAAABnrkStAAAAAAAAAAEAAAAAAAAAGAAAAAMAAAAAAAAAAAAAAAC4YTsgfBnWL+VK8itDD/gCnqtirv5cWHO3MXxqONNVmvsvbA+ypiNL9AWBaQhdhXtpba5tIv74SARYfnqYLTICAAAAAMH9pZirYl7himeZ8T6ddEuJwyaE/xqaJTNnneY4XZSXAAAAAwAAABIAAAABg53yKxwbQoljlymK88aXMGcZomvOAI8tWIoxYZ7dk3UAAAASAAAAAdeSi3LCcDzP6vfrn/TvTVBKVai5efybRQ6iyEK00c5hAAAAAwAAAAYAAAAAAAAAAAAAAAA=';

bun_tests/9.index.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { strict as assert } from "node:assert";
2+
import {
3+
Address,
4+
Keypair,
5+
Networks,
6+
buildAuthorizationEntryPreimage,
7+
buildWithDelegatesEntry,
8+
hash,
9+
xdr,
10+
} from "@stellar/stellar-sdk";
11+
import {
12+
assertSignatureExpirationLedger,
13+
buildSignaturePayload,
14+
getAddressCredentials,
15+
usesAddressBoundPayload,
16+
} from "../src/kit";
17+
18+
function makeAccount(seedByte: number): string {
19+
return Keypair.fromRawEd25519Seed(Buffer.alloc(32, seedByte)).publicKey();
20+
}
21+
22+
function makeInvocation(): xdr.SorobanAuthorizedInvocation {
23+
return new xdr.SorobanAuthorizedInvocation({
24+
function: xdr.SorobanAuthorizedFunction.sorobanAuthorizedFunctionTypeContractFn(
25+
new xdr.InvokeContractArgs({
26+
contractAddress: xdr.ScAddress.scAddressTypeContract(hash(Buffer.from("contract"))),
27+
functionName: "do_it",
28+
args: [],
29+
})
30+
),
31+
subInvocations: [],
32+
});
33+
}
34+
35+
function makeAddressCredentials(address: string, expiration = 1): xdr.SorobanAddressCredentials {
36+
return new xdr.SorobanAddressCredentials({
37+
address: Address.fromString(address).toScAddress(),
38+
nonce: xdr.Int64.fromString("7"),
39+
signatureExpirationLedger: expiration,
40+
signature: xdr.ScVal.scvVoid(),
41+
});
42+
}
43+
44+
function makeAuthEntry(address: string): xdr.SorobanAuthorizationEntry {
45+
return new xdr.SorobanAuthorizationEntry({
46+
credentials: xdr.SorobanCredentials.sorobanCredentialsAddress(
47+
makeAddressCredentials(address)
48+
),
49+
rootInvocation: makeInvocation(),
50+
});
51+
}
52+
53+
const networkPassphrase = Networks.TESTNET;
54+
55+
{
56+
const account = makeAccount(1);
57+
const entry = makeAuthEntry(account);
58+
const credentials = getAddressCredentials(entry.credentials());
59+
60+
assert.equal(entry.credentials().switch().name, "sorobanCredentialsAddress");
61+
assert.equal(Address.fromScAddress(credentials.address()).toString(), account);
62+
assert.equal(usesAddressBoundPayload(entry.credentials()), false);
63+
}
64+
65+
{
66+
const entry = makeAuthEntry(makeAccount(2));
67+
const payload = buildSignaturePayload(networkPassphrase, entry, 123);
68+
const credentials = getAddressCredentials(entry.credentials());
69+
const expectedEntry = xdr.SorobanAuthorizationEntry.fromXDR(entry.toXDR());
70+
const expectedPreimage = buildAuthorizationEntryPreimage(
71+
expectedEntry,
72+
123,
73+
networkPassphrase
74+
);
75+
76+
assert.deepEqual(payload, hash(expectedPreimage.toXDR()));
77+
assert.equal(credentials.signatureExpirationLedger(), 123);
78+
}
79+
80+
{
81+
const baseEntry = makeAuthEntry(makeAccount(3));
82+
const credentials = getAddressCredentials(baseEntry.credentials());
83+
const credentialFactory = xdr.SorobanCredentials as unknown as {
84+
sorobanCredentialsAddressV2: (
85+
credentials: xdr.SorobanAddressCredentials
86+
) => xdr.SorobanCredentials;
87+
};
88+
const addressV2Entry = new xdr.SorobanAuthorizationEntry({
89+
credentials: credentialFactory.sorobanCredentialsAddressV2(credentials),
90+
rootInvocation: baseEntry.rootInvocation(),
91+
});
92+
const expectedEntry = xdr.SorobanAuthorizationEntry.fromXDR(addressV2Entry.toXDR());
93+
const expectedPreimage = buildAuthorizationEntryPreimage(
94+
expectedEntry,
95+
123,
96+
networkPassphrase
97+
);
98+
99+
assert.equal(addressV2Entry.credentials().switch().name, "sorobanCredentialsAddressV2");
100+
assert.equal(usesAddressBoundPayload(addressV2Entry.credentials()), true);
101+
assert.deepEqual(
102+
buildSignaturePayload(networkPassphrase, addressV2Entry, 123),
103+
hash(expectedPreimage.toXDR())
104+
);
105+
assert.equal(credentials.signatureExpirationLedger(), 123);
106+
}
107+
108+
{
109+
const delegatedEntry = buildWithDelegatesEntry({
110+
entry: makeAuthEntry(makeAccount(4)),
111+
validUntilLedgerSeq: 123,
112+
delegates: [{ address: makeAccount(5) }],
113+
});
114+
const expectedEntry = xdr.SorobanAuthorizationEntry.fromXDR(delegatedEntry.toXDR());
115+
const expectedPreimage = buildAuthorizationEntryPreimage(
116+
expectedEntry,
117+
123,
118+
networkPassphrase
119+
);
120+
121+
assert.equal(
122+
delegatedEntry.credentials().switch().name,
123+
"sorobanCredentialsAddressWithDelegates"
124+
);
125+
assert.equal(usesAddressBoundPayload(delegatedEntry.credentials()), true);
126+
assert.deepEqual(
127+
buildSignaturePayload(networkPassphrase, delegatedEntry, 123),
128+
hash(expectedPreimage.toXDR())
129+
);
130+
assert.equal(getAddressCredentials(delegatedEntry.credentials()).signatureExpirationLedger(), 123);
131+
}
132+
133+
{
134+
const fakeAddressV2Credentials = {
135+
switch: () => ({ name: "sorobanCredentialsAddressV2" }),
136+
} as unknown as xdr.SorobanCredentials;
137+
138+
assert.throws(
139+
() => getAddressCredentials(fakeAddressV2Credentials),
140+
/ADDRESS_V2 credentials require an SDK with Protocol 27 credential support/
141+
);
142+
}
143+
144+
{
145+
const fakeDelegatedCredentials = {
146+
switch: () => ({ name: "sorobanCredentialsAddressWithDelegates" }),
147+
} as unknown as xdr.SorobanCredentials;
148+
149+
assert.equal(usesAddressBoundPayload(fakeDelegatedCredentials), true);
150+
assert.throws(
151+
() => getAddressCredentials(fakeDelegatedCredentials),
152+
/ADDRESS_WITH_DELEGATES credentials require an SDK with Protocol 27 credential support/
153+
);
154+
}
155+
156+
{
157+
assert.doesNotThrow(() => assertSignatureExpirationLedger(0));
158+
assert.doesNotThrow(() => assertSignatureExpirationLedger(0xffffffff));
159+
assert.throws(() => assertSignatureExpirationLedger(-1), /uint32 integer/);
160+
assert.throws(() => assertSignatureExpirationLedger(0x100000000), /uint32 integer/);
161+
assert.throws(() => assertSignatureExpirationLedger(1.5), /uint32 integer/);
162+
assert.throws(() => assertSignatureExpirationLedger(Number.NaN), /uint32 integer/);
163+
}
164+
165+
{
166+
const entry = makeAuthEntry(makeAccount(4));
167+
const credentials = getAddressCredentials(entry.credentials());
168+
169+
assert.throws(
170+
() => buildSignaturePayload(networkPassphrase, entry, 1.5),
171+
/Soroban signature expiration ledger must be a uint32 integer/
172+
);
173+
assert.equal(credentials.signatureExpirationLedger(), 1);
174+
}
175+
176+
console.log("Protocol 27 auth helper tests passed");

demo/package.json

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,25 @@
99
"check": "svelte-check --tsconfig ./tsconfig.json"
1010
},
1111
"devDependencies": {
12-
"@stellar/stellar-sdk": "^14.1.1",
12+
"@stellar/stellar-sdk": "^16.0.1",
1313
"@sveltejs/vite-plugin-svelte": "^3.1.2",
14-
"@tsconfig/svelte": "^5.0.5",
14+
"@tsconfig/svelte": "^5.0.8",
1515
"base64url": "^3.0.1",
1616
"buffer": "^6.0.3",
1717
"passkey-kit": "link:..",
1818
"svelte": "^4.2.20",
19-
"svelte-check": "^4.3.1",
19+
"svelte-check": "^4.7.2",
2020
"tslib": "^2.8.1",
21-
"typescript": "^5.9.2",
22-
"vite": "^5.4.19",
21+
"typescript": "^5.9.3",
22+
"vite": "^5.4.21",
2323
"vite-plugin-filter-replace": "^0.1.14"
2424
},
2525
"pnpm": {
2626
"overrides": {
2727
"@stellar/stellar-sdk": "$@stellar/stellar-sdk"
2828
}
29+
},
30+
"engines": {
31+
"node": ">=22.18.0"
2932
}
3033
}

0 commit comments

Comments
 (0)