@chainberry/trust-wallet-core 2.0.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{TrustWalletCoreModule.podspec → ChainberryTrustWalletCoreModule.podspec} +2 -2
- package/README.md +15 -22
- package/android/build.gradle +29 -6
- package/android/libs/README.md +34 -0
- package/android/libs/com/trustwallet/wallet-core/4.1.19/wallet-core-4.1.19.aar +0 -0
- package/android/libs/com/trustwallet/wallet-core/4.1.19/wallet-core-4.1.19.aar.md5 +1 -0
- package/android/libs/com/trustwallet/wallet-core/4.1.19/wallet-core-4.1.19.aar.sha1 +1 -0
- package/android/libs/com/trustwallet/wallet-core/4.1.19/wallet-core-4.1.19.pom +22 -0
- package/android/libs/com/trustwallet/wallet-core/4.1.19/wallet-core-4.1.19.pom.md5 +1 -0
- package/android/libs/com/trustwallet/wallet-core/4.1.19/wallet-core-4.1.19.pom.sha1 +1 -0
- package/android/libs/com/trustwallet/wallet-core-proto/4.1.19/wallet-core-proto-4.1.19.jar +0 -0
- package/android/libs/com/trustwallet/wallet-core-proto/4.1.19/wallet-core-proto-4.1.19.jar.md5 +1 -0
- package/android/libs/com/trustwallet/wallet-core-proto/4.1.19/wallet-core-proto-4.1.19.jar.sha1 +1 -0
- package/android/libs/com/trustwallet/wallet-core-proto/4.1.19/wallet-core-proto-4.1.19.pom +21 -0
- package/android/libs/com/trustwallet/wallet-core-proto/4.1.19/wallet-core-proto-4.1.19.pom.md5 +1 -0
- package/android/libs/com/trustwallet/wallet-core-proto/4.1.19/wallet-core-proto-4.1.19.pom.sha1 +1 -0
- package/android/libs/download.sh +52 -0
- package/android/src/androidTest/java/com/chainberry/trustwalletcore/AddressDerivationConformanceTest.kt +106 -0
- package/android/src/androidTest/java/com/chainberry/trustwalletcore/SigningConformanceTest.kt +186 -0
- package/android/src/main/java/com/chainberry/trustwalletcore/AmountParsing.kt +45 -0
- package/android/src/main/java/com/chainberry/trustwalletcore/Bech32.kt +68 -0
- package/android/src/main/java/com/chainberry/trustwalletcore/ChainSigning.kt +526 -0
- package/android/src/main/java/com/chainberry/trustwalletcore/ChainberryTrustWalletCoreModule.kt +147 -0
- package/android/src/main/java/com/chainberry/trustwalletcore/NativeWalletStore.kt +796 -0
- package/android/src/test/java/com/chainberry/trustwalletcore/AmountParsingConformanceTest.kt +57 -0
- package/android/src/test/java/com/chainberry/trustwalletcore/Bech32Test.kt +35 -0
- package/android/src/test/java/com/chainberry/trustwalletcore/NativeWalletStoreTest.kt +274 -0
- package/expo-module.config.json +3 -2
- package/ios/AmountParsing.swift +62 -0
- package/ios/Bech32.swift +66 -0
- package/ios/ChainSigning.swift +602 -0
- package/ios/ChainberryTrustWalletCoreModule.swift +231 -0
- package/ios/NativeWalletStore.swift +232 -0
- package/package.json +4 -3
- package/src/index.ts +27 -12
- package/android/src/main/java/expo/modules/trustwalletcore/ChainSigning.kt +0 -299
- package/android/src/main/java/expo/modules/trustwalletcore/NativeWalletStore.kt +0 -182
- package/android/src/main/java/expo/modules/trustwalletcore/TrustWalletCoreModule.kt +0 -91
- package/ios/TrustWalletCoreModule.swift +0 -107
|
@@ -3,7 +3,7 @@ require 'json'
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
4
|
|
|
5
5
|
Pod::Spec.new do |s|
|
|
6
|
-
s.name = '
|
|
6
|
+
s.name = 'ChainberryTrustWalletCoreModule'
|
|
7
7
|
s.version = package['version']
|
|
8
8
|
s.summary = package['description']
|
|
9
9
|
s.description = s.summary
|
|
@@ -17,5 +17,5 @@ Pod::Spec.new do |s|
|
|
|
17
17
|
s.source_files = 'ios/**/*.{h,m,mm,swift}'
|
|
18
18
|
|
|
19
19
|
s.dependency 'ExpoModulesCore'
|
|
20
|
-
s.dependency 'TrustWalletCore'
|
|
20
|
+
s.dependency 'TrustWalletCore', '4.1.19'
|
|
21
21
|
end
|
package/README.md
CHANGED
|
@@ -12,26 +12,11 @@ npx expo install @chainberry/trust-wallet-core
|
|
|
12
12
|
|
|
13
13
|
This is an Expo config plugin module with native Android/iOS code, so it requires a development build (`expo prebuild` / EAS Build) — it will not work in Expo Go.
|
|
14
14
|
|
|
15
|
-
### Android: GitHub
|
|
15
|
+
### Android: no GitHub credentials needed
|
|
16
16
|
|
|
17
|
-
Trust Wallet Core's Android artifact is published to GitHub Packages, which requires authentication even though the package itself is public.
|
|
17
|
+
Trust Wallet Core's Android artifact is only published to GitHub Packages, which requires authentication even though the package itself is public. To avoid making that a requirement for every dev/CI machine, `com.trustwallet:wallet-core` and its `wallet-core-proto` dependency are vendored locally as a plain Maven repo in `android/libs/` — `android/build.gradle` resolves from there first, so a normal `./gradlew` build needs no credentials at all. See `android/libs/README.md` for how it's kept up to date.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```sh
|
|
22
|
-
# Env vars (CI / one-off terminal)
|
|
23
|
-
export GITHUB_ACTOR=your-github-username
|
|
24
|
-
export GITHUB_TOKEN=ghp_xxxxxxxxxxxx # classic PAT, read:packages scope only
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
or add to `~/.gradle/gradle.properties` (permanent local dev setup):
|
|
28
|
-
|
|
29
|
-
```properties
|
|
30
|
-
gpr.user=your-github-username
|
|
31
|
-
gpr.key=ghp_xxxxxxxxxxxx
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Generate a token at github.com/settings/tokens → "classic" → check `read:packages`.
|
|
19
|
+
GitHub Packages auth is still needed (via `GITHUB_ACTOR`/`GITHUB_TOKEN` env vars or `gpr.user`/`gpr.key` in `~/.gradle/gradle.properties`) only when bumping the pinned wallet-core version — see `android/libs/download.sh`.
|
|
35
20
|
|
|
36
21
|
### iOS
|
|
37
22
|
|
|
@@ -58,8 +43,8 @@ const mnemonic = await exportMnemonic(walletId);
|
|
|
58
43
|
|
|
59
44
|
## API
|
|
60
45
|
|
|
61
|
-
- `createWallet(strength = 128
|
|
62
|
-
- `importWallet(mnemonic
|
|
46
|
+
- `createWallet(strength = 128)` — generates a new BIP-39 mnemonic and persists it natively (Keychain on iOS / Keystore-backed file on Android, biometry-or-passcode gated). Returns `{ walletId, addresses }` — the mnemonic itself never leaves native code. No BIP-39 passphrase support: `signTransaction` always reconstructs the wallet with an empty passphrase, so a caller-supplied one would derive addresses from a seed different from the one actually used to sign.
|
|
47
|
+
- `importWallet(mnemonic)` — validates and persists an existing mnemonic the same way. The `mnemonic` argument is a one-time exposure from the caller (e.g. a text-entry backup-restore screen); discard your own copy immediately after this call resolves.
|
|
63
48
|
- `listWallets()` — returns `{ walletId, addresses }[]` for every persisted wallet, reading only the ungated metadata store. No biometric prompt.
|
|
64
49
|
- `deleteWallet(walletId)` — removes the wallet's native key material and metadata entry. Irreversible; not biometric-gated (deleting reveals nothing, so this is a UX confirmation concern, not a key-secrecy one).
|
|
65
50
|
- `signTransaction(walletId, chain, unsignedTx)` — triggers a native biometry/passcode prompt, then derives the key and signs entirely inside native code. Returns `{ signedTx, meta? }`; `meta` currently only carries TON's `txHash`.
|
|
@@ -69,11 +54,19 @@ const mnemonic = await exportMnemonic(walletId);
|
|
|
69
54
|
|
|
70
55
|
Mnemonic and derived private keys are generated, persisted, and used for signing entirely inside this module's native code (Swift/Kotlin) — they are never serialized back across the JS bridge, with the single exception of `exportMnemonic`'s explicit backup flow. This is a deliberate change from this module's earlier version, which returned raw private keys to JS on every address derivation; that let a compromised/malicious JS dependency, an attached JS debugger, or a JS-heap memory dump read wallet secrets in full. Now the JS runtime never holds them at all.
|
|
71
56
|
|
|
72
|
-
Storage: one biometry-or-passcode-gated secret per wallet (`SecAccessControl` + iOS Keychain;
|
|
57
|
+
Storage: one biometry-or-passcode-gated secret per wallet (`SecAccessControl` + iOS Keychain; an Android Keystore AES key gating an encrypted on-disk file), plus a separate, ungated metadata entry (`walletId` → addresses) for read-only UI that shouldn't need a biometric prompt just to show an address or balance.
|
|
58
|
+
|
|
59
|
+
**Android hardware backing is best-effort, not guaranteed.** Key creation requests StrongBox first (API 28+), falling back to a plain Keystore key (which Keymaster backs with a TEE on virtually all real devices, but which can be software-only on an emulator or a device with no secure hardware at all) on `StrongBoxUnavailableException` or below API 28. The actual security level achieved is verified via `KeyInfo` right after creation and logged (`NativeWalletStore` logcat tag) — a software-only key is tolerated rather than blocking wallet creation, so this is a best-effort hardening measure, not an enforced guarantee. Don't assume every wallet on every device is hardware-backed; check the logs on a specific device/build if that matters for your threat model.
|
|
73
60
|
|
|
74
61
|
Bitcoin Cash is derivation-only: sending BCH is unsupported both here and upstream (`chainberry-wallet`'s self-custody transaction preparation has no BCH case), so this module only derives its address.
|
|
75
62
|
|
|
76
|
-
|
|
63
|
+
Both platforms pin **Trust Wallet Core 4.1.19** (`com.trustwallet:wallet-core:4.1.19` in `android/build.gradle`; `s.dependency 'TrustWalletCore', '4.1.19'` in the podspec).
|
|
64
|
+
|
|
65
|
+
**Address derivation is verified for all 10 chains** in `conformance/address-derivation-vectors.json`, asserted by the Android instrumented test (`src/androidTest/.../AddressDerivationConformanceTest.kt`; run via `./gradlew connectedDebugAndroidTest`). Methodology: real on-device 4.1.19 addresses were harvested from the instrumented test running on an emulator, cross-checked against an independent derivation via the WASM build (`@trustwallet/wallet-core` 3.3.3, run standalone in Node — a different upstream release than the pinned 4.1.19, used only as a second data point); on-device 4.1.19 is authoritative wherever the two disagree, since that's what the app actually ships. They agreed on 6 of 7 previously-pending chains exactly; `ton` disagreed only in address-flag encoding (bounceable vs. non-bounceable — same underlying key/hash, see that fixture entry's `_note`). This same run also caught a real bug: the Ethereum/BNB/Polygon address that had been marked `"verified"` since before this pass was actually wrong — the instrumented test that should have caught it had never successfully executed (two pre-existing bugs: `coin.name()` didn't compile against this Kotlin binding, and no `testInstrumentationRunner` was configured, so `connectedAndroidTest` silently ran "0 tests" instead of failing). Both are fixed now; see the test file's header comment for details.
|
|
66
|
+
|
|
67
|
+
**Byte-for-byte signing output is verified for 8 of 9 signable chains** in `conformance/signing-vectors.json`, asserted by `src/androidTest/.../SigningConformanceTest.kt`. Each vector calls `ChainSigner.sign()` — the same call path production code uses — with a fixed, deterministic (not necessarily broadcast-valid) unsigned tx. `ton` is verified but *not* byte-exact-asserted: `signTon()` embeds a wall-clock `expireAt` into the signed payload, so its output legitimately differs every run — the test instead checks the output is a well-formed signed BOC. `bitcoincash` has no signing vector (sending is unsupported, see above).
|
|
68
|
+
|
|
69
|
+
**iOS gap — not closed by this pass.** No Swift/Xcode toolchain was available in the environment that did this verification, so none of the above has been independently confirmed on iOS. `ChainKey.coinType` maps identically to Android's, so derivation *should* match, but this hasn't been checked on-device. `ConformanceTests/` currently has no address-derivation or signing test target at all (only the pre-existing amount-parsing one) — before this finding is fully closed for both platforms, an iOS engineer needs to (1) add `AddressDerivationConformanceTests`/`SigningConformanceTests` targets mirroring the Android ones, (2) run them against these same fixtures on a Mac, and (3) specifically resolve the **Tron divergence**: Android's `signTron` signs just the `txId` digest and reassembles JSON in app code, while iOS's hands wallet-core the full `rawJson` and returns its own reconstruction — `conformance/signing-vectors.json`'s `tron` entry spells out exactly what to check (the embedded signature hex must match Android's byte-for-byte; a mismatch there is a real bug, not a formatting difference).
|
|
77
70
|
|
|
78
71
|
## License
|
|
79
72
|
|
package/android/build.gradle
CHANGED
|
@@ -12,12 +12,16 @@ apply from: new File(
|
|
|
12
12
|
applyKotlinExpoModulesCorePlugin()
|
|
13
13
|
|
|
14
14
|
android {
|
|
15
|
-
namespace '
|
|
16
|
-
compileSdkVersion
|
|
15
|
+
namespace 'com.chainberry.trustwalletcore'
|
|
16
|
+
compileSdkVersion 36
|
|
17
17
|
|
|
18
18
|
defaultConfig {
|
|
19
19
|
minSdkVersion 24
|
|
20
|
-
targetSdkVersion
|
|
20
|
+
targetSdkVersion 36
|
|
21
|
+
// Without this, AGP defaults to the legacy android.test.InstrumentationTestRunner, which
|
|
22
|
+
// cannot discover @RunWith(AndroidJUnit4::class)-annotated tests at all (connectedAndroidTest
|
|
23
|
+
// silently reports "0 tests"/"No tests found" instead of running AddressDerivationConformanceTest).
|
|
24
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
compileOptions {
|
|
@@ -30,9 +34,14 @@ android {
|
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
// ─── wallet-core
|
|
34
|
-
//
|
|
35
|
-
//
|
|
37
|
+
// ─── wallet-core artifact ──────────────────────────────────────────────────
|
|
38
|
+
// com.trustwallet:wallet-core and its wallet-core-proto dependency are vendored
|
|
39
|
+
// in libs/ as a plain local Maven repo, so a normal build needs NO GitHub
|
|
40
|
+
// credentials at all — see libs/README.md for how it got there and how to
|
|
41
|
+
// re-vendor when bumping the pinned version below.
|
|
42
|
+
//
|
|
43
|
+
// The GitHub Packages repo is kept as a fallback (e.g. resolving a version not
|
|
44
|
+
// yet vendored) and needs auth even for this public package:
|
|
36
45
|
// 1. Env vars GITHUB_ACTOR / GITHUB_TOKEN (CI / one-off terminal)
|
|
37
46
|
// 2. Gradle props gpr.user / gpr.key in (permanent dev setup)
|
|
38
47
|
// ~/.gradle/gradle.properties
|
|
@@ -49,6 +58,8 @@ def githubToken = System.getenv("GITHUB_TOKEN")
|
|
|
49
58
|
?: (project.hasProperty("gpr.key") ? project.property("gpr.key") : "")
|
|
50
59
|
|
|
51
60
|
repositories {
|
|
61
|
+
maven { url = uri("$projectDir/libs") }
|
|
62
|
+
|
|
52
63
|
maven {
|
|
53
64
|
url = uri("https://maven.pkg.github.com/trustwallet/wallet-core")
|
|
54
65
|
credentials {
|
|
@@ -66,4 +77,16 @@ dependencies {
|
|
|
66
77
|
implementation 'com.google.protobuf:protobuf-javalite:3.21.9'
|
|
67
78
|
// BiometricPrompt (biometry-or-device-credential gating on the Keystore-backed wallet cipher)
|
|
68
79
|
implementation 'androidx.biometric:biometric:1.1.0'
|
|
80
|
+
|
|
81
|
+
// JVM unit tests (src/test) — AmountParsingConformanceTest reads the shared JSON fixture
|
|
82
|
+
// in ../conformance/. Use the pure-Java org.json artifact, not android.jar's stubbed
|
|
83
|
+
// org.json (which throws "Stub!" outside Robolectric/instrumented tests).
|
|
84
|
+
testImplementation 'junit:junit:4.13.2'
|
|
85
|
+
testImplementation 'org.json:json:20240303'
|
|
86
|
+
|
|
87
|
+
// Instrumented tests (src/androidTest) — AddressDerivationConformanceTest needs the
|
|
88
|
+
// TrustWalletCore JNI library, which can only load on a real device or emulator.
|
|
89
|
+
// Run via: ./gradlew connectedDebugAndroidTest
|
|
90
|
+
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
|
91
|
+
androidTestImplementation 'androidx.test:runner:1.5.2'
|
|
69
92
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Vendored wallet-core (local Maven repo)
|
|
2
|
+
|
|
3
|
+
`com.trustwallet:wallet-core` (and its `wallet-core-proto` dependency) are only
|
|
4
|
+
published to GitHub Packages, which requires authentication even to download a
|
|
5
|
+
public package. To spare every dev/CI machine from needing a
|
|
6
|
+
`read:packages` PAT, the resolved `.aar`/`.jar` + `.pom` files are vendored
|
|
7
|
+
here in a plain Maven-layout directory:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
libs/com/trustwallet/wallet-core/4.1.19/wallet-core-4.1.19.{aar,pom}
|
|
11
|
+
libs/com/trustwallet/wallet-core-proto/4.1.19/wallet-core-proto-4.1.19.{jar,pom}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`../build.gradle` points its `repositories {}` block at this directory
|
|
15
|
+
*before* the GitHub Packages fallback, so a normal build resolves entirely
|
|
16
|
+
from here — no credentials needed.
|
|
17
|
+
|
|
18
|
+
Files were downloaded from `maven.pkg.github.com/trustwallet/wallet-core` on
|
|
19
|
+
2026-09-04 and their sha1 checksums verified against GitHub's own
|
|
20
|
+
`.sha1` files (still alongside each artifact here).
|
|
21
|
+
|
|
22
|
+
## Bumping the pinned version
|
|
23
|
+
|
|
24
|
+
1. `./download.sh <new-version>` (needs `GITHUB_ACTOR`/`GITHUB_TOKEN` env vars,
|
|
25
|
+
or `gpr.user`/`gpr.key` in `~/.gradle/gradle.properties` — same credentials
|
|
26
|
+
the GitHub Packages fallback in `build.gradle` already uses).
|
|
27
|
+
2. Update the pinned version in `../build.gradle`
|
|
28
|
+
(`implementation 'com.trustwallet:wallet-core:...'`) and in
|
|
29
|
+
`../../ChainberryTrustWalletCoreModule.podspec` (`s.dependency 'TrustWalletCore', '...'`)
|
|
30
|
+
to keep both platforms on the same wallet-core release.
|
|
31
|
+
3. Delete the old version's directories under `com/trustwallet/wallet-core*/`
|
|
32
|
+
here (or leave them if you want to keep an easy rollback).
|
|
33
|
+
4. Re-run the conformance suite (`conformance/`) — a wallet-core bump can
|
|
34
|
+
change derivation/signing output.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3f07349ecbe013f2098684cc70cef49e
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
58c6ee87c3ab4fb90c6ef4e9ba4f78260740ca16
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
3
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
4
|
+
<!-- This module was also published with a richer model, Gradle metadata, -->
|
|
5
|
+
<!-- which should be used instead. Do not delete the following line which -->
|
|
6
|
+
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
|
|
7
|
+
<!-- that they should prefer consuming it instead. -->
|
|
8
|
+
<!-- do_not_remove: published-with-gradle-metadata -->
|
|
9
|
+
<modelVersion>4.0.0</modelVersion>
|
|
10
|
+
<groupId>com.trustwallet</groupId>
|
|
11
|
+
<artifactId>wallet-core</artifactId>
|
|
12
|
+
<version>4.1.19</version>
|
|
13
|
+
<packaging>aar</packaging>
|
|
14
|
+
<dependencies>
|
|
15
|
+
<dependency>
|
|
16
|
+
<groupId>com.trustwallet</groupId>
|
|
17
|
+
<artifactId>wallet-core-proto</artifactId>
|
|
18
|
+
<version>4.1.19</version>
|
|
19
|
+
<scope>compile</scope>
|
|
20
|
+
</dependency>
|
|
21
|
+
</dependencies>
|
|
22
|
+
</project>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
d4a399c80d4f896ae4d4ea6013f1f15f
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
a00894bc91fbbbf496fd3792484082334363e0fe
|
|
Binary file
|
package/android/libs/com/trustwallet/wallet-core-proto/4.1.19/wallet-core-proto-4.1.19.jar.md5
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2c47413fd480c3ae3a4245f8487a343e
|
package/android/libs/com/trustwallet/wallet-core-proto/4.1.19/wallet-core-proto-4.1.19.jar.sha1
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1a68899feeb645d9895264d4c58e046dbcb3d528
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
3
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
4
|
+
<!-- This module was also published with a richer model, Gradle metadata, -->
|
|
5
|
+
<!-- which should be used instead. Do not delete the following line which -->
|
|
6
|
+
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
|
|
7
|
+
<!-- that they should prefer consuming it instead. -->
|
|
8
|
+
<!-- do_not_remove: published-with-gradle-metadata -->
|
|
9
|
+
<modelVersion>4.0.0</modelVersion>
|
|
10
|
+
<groupId>com.trustwallet</groupId>
|
|
11
|
+
<artifactId>wallet-core-proto</artifactId>
|
|
12
|
+
<version>4.1.19</version>
|
|
13
|
+
<dependencies>
|
|
14
|
+
<dependency>
|
|
15
|
+
<groupId>com.google.protobuf</groupId>
|
|
16
|
+
<artifactId>protobuf-javalite</artifactId>
|
|
17
|
+
<version>3.22.3</version>
|
|
18
|
+
<scope>compile</scope>
|
|
19
|
+
</dependency>
|
|
20
|
+
</dependencies>
|
|
21
|
+
</project>
|
package/android/libs/com/trustwallet/wallet-core-proto/4.1.19/wallet-core-proto-4.1.19.pom.md5
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
e3987a3948a049ac60b57fa808b8f8ae
|
package/android/libs/com/trustwallet/wallet-core-proto/4.1.19/wallet-core-proto-4.1.19.pom.sha1
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
90383b24ce13303b7c7a435ba815209115fa7d06
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Re-vendors com.trustwallet:wallet-core (+ its wallet-core-proto dependency)
|
|
3
|
+
# from GitHub Packages into this directory, laid out as a plain local Maven
|
|
4
|
+
# repo (com/<group>/<artifact>/<version>/...). build.gradle points at this
|
|
5
|
+
# directory first, so a normal build needs no GitHub credentials — only
|
|
6
|
+
# re-running this script (to bump the pinned version, or the first time
|
|
7
|
+
# someone sets this up) needs a token.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# GITHUB_ACTOR=you GITHUB_TOKEN=ghp_xxx ./download.sh 4.1.19
|
|
11
|
+
# or rely on gpr.user / gpr.key in ~/.gradle/gradle.properties (same
|
|
12
|
+
# credentials used by build.gradle's GitHub Packages fallback).
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
VERSION="${1:?Usage: download.sh <wallet-core version, e.g. 4.1.19>}"
|
|
16
|
+
LIBS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
|
|
18
|
+
GITHUB_USER="${GITHUB_ACTOR:-}"
|
|
19
|
+
GITHUB_PASS="${GITHUB_TOKEN:-}"
|
|
20
|
+
if [ -z "$GITHUB_USER" ] || [ -z "$GITHUB_PASS" ]; then
|
|
21
|
+
GRADLE_PROPS="$HOME/.gradle/gradle.properties"
|
|
22
|
+
if [ -f "$GRADLE_PROPS" ]; then
|
|
23
|
+
GITHUB_USER="${GITHUB_USER:-$(grep '^gpr.user=' "$GRADLE_PROPS" | cut -d= -f2-)}"
|
|
24
|
+
GITHUB_PASS="${GITHUB_PASS:-$(grep '^gpr.key=' "$GRADLE_PROPS" | cut -d= -f2-)}"
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
if [ -z "$GITHUB_USER" ] || [ -z "$GITHUB_PASS" ]; then
|
|
28
|
+
echo "error: no GitHub credentials found (env vars or ~/.gradle/gradle.properties gpr.user/gpr.key)" >&2
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
REGISTRY="https://maven.pkg.github.com/trustwallet/wallet-core"
|
|
33
|
+
|
|
34
|
+
# module:packaging pairs — wallet-core is an .aar, wallet-core-proto is a plain .jar
|
|
35
|
+
MODULES=("wallet-core:aar" "wallet-core-proto:jar")
|
|
36
|
+
|
|
37
|
+
for entry in "${MODULES[@]}"; do
|
|
38
|
+
artifact="${entry%%:*}"
|
|
39
|
+
ext="${entry##*:}"
|
|
40
|
+
dest="$LIBS_DIR/com/trustwallet/$artifact/$VERSION"
|
|
41
|
+
mkdir -p "$dest"
|
|
42
|
+
base="$REGISTRY/com/trustwallet/$artifact/$VERSION"
|
|
43
|
+
for file in "$artifact-$VERSION.pom" "$artifact-$VERSION.$ext" "$artifact-$VERSION.pom.sha1" "$artifact-$VERSION.$ext.sha1"; do
|
|
44
|
+
echo "fetching $file"
|
|
45
|
+
curl -sS -L -f -u "$GITHUB_USER:$GITHUB_PASS" -o "$dest/$file" "$base/$file"
|
|
46
|
+
done
|
|
47
|
+
# verify what we just wrote against GitHub's own sha1
|
|
48
|
+
( cd "$dest" && sha1sum -c "$artifact-$VERSION.pom.sha1" "$artifact-$VERSION.$ext.sha1" )
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
echo "done — vendored wallet-core $VERSION into $LIBS_DIR"
|
|
52
|
+
echo "remember to bump the version in ../build.gradle (dependencies block) and the podspec, and delete the old version's directories above if this is a version bump rather than a first-time vendor."
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
package com.chainberry.trustwalletcore
|
|
2
|
+
|
|
3
|
+
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
4
|
+
import org.junit.Assert.assertEquals
|
|
5
|
+
import org.junit.Assert.assertNotEquals
|
|
6
|
+
import org.junit.Test
|
|
7
|
+
import org.junit.runner.RunWith
|
|
8
|
+
import wallet.core.jni.CoinType
|
|
9
|
+
import wallet.core.jni.HDWallet
|
|
10
|
+
|
|
11
|
+
// Instrumented test — must run on a device or emulator (needs the TrustWalletCore JNI library).
|
|
12
|
+
// Run via: ./gradlew connectedDebugAndroidTest
|
|
13
|
+
//
|
|
14
|
+
// Verifies that HDWallet.getAddressForCoin produces the expected address for every chain
|
|
15
|
+
// under WalletCore 4.1.19 (the pinned version). The test mnemonic is the BIP39 standard
|
|
16
|
+
// test vector — never use it with real funds.
|
|
17
|
+
//
|
|
18
|
+
// All 10 chains were verified on-device (real emulator, WalletCore 4.1.19 JNI) on 2026-08-27,
|
|
19
|
+
// cross-checked against an independent derivation via the WASM build (@trustwallet/wallet-core
|
|
20
|
+
// 3.3.3, run in plain Node — see conformance/address-derivation-vectors.json's _note). On-device
|
|
21
|
+
// 4.1.19 is authoritative wherever the two disagree.
|
|
22
|
+
//
|
|
23
|
+
// IMPORTANT — this test did not actually run before this pass. Two pre-existing bugs prevented
|
|
24
|
+
// it from ever executing: (1) `coin.name()` doesn't compile against this Kotlin CoinType binding
|
|
25
|
+
// (name is a property, not a method — fixed here), and (2) no `testInstrumentationRunner` was
|
|
26
|
+
// configured in build.gradle, so AGP defaulted to the legacy runner, which cannot discover
|
|
27
|
+
// @RunWith(AndroidJUnit4::class) tests at all ("0 tests"/"No tests found", not a real pass).
|
|
28
|
+
// Once actually run, it immediately caught a real bug: the ETHEREUM/SMARTCHAIN "verified" address
|
|
29
|
+
// below was wrong (0x9d8A62f6...) — never actually checked against real WalletCore output despite
|
|
30
|
+
// being marked "verified". The corrected value below is the real on-device 4.1.19 result.
|
|
31
|
+
//
|
|
32
|
+
// For any NEW chain added to ChainKey in the future: add its CoinType to PENDING, run this test,
|
|
33
|
+
// read the printed address in logcat, verify it independently (e.g. against a TWC 4.1.19 REPL or
|
|
34
|
+
// the iOS companion test — see ConformanceTests/), then move it into VERIFIED.
|
|
35
|
+
//
|
|
36
|
+
// Human-readable source of truth: conformance/address-derivation-vectors.json.
|
|
37
|
+
@RunWith(AndroidJUnit4::class)
|
|
38
|
+
class AddressDerivationConformanceTest {
|
|
39
|
+
|
|
40
|
+
companion object {
|
|
41
|
+
init { System.loadLibrary("TrustWalletCore") }
|
|
42
|
+
|
|
43
|
+
const val MNEMONIC = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
|
44
|
+
|
|
45
|
+
// Chains with expected addresses confirmed against WalletCore 4.1.19, on a real device/emulator.
|
|
46
|
+
// ETH / BNB / POL all share CoinType.ETHEREUM (same secp256k1 key, BIP44 m/44'/60'/0'/0/0).
|
|
47
|
+
val VERIFIED = mapOf(
|
|
48
|
+
CoinType.ETHEREUM to "0x9858EfFD232B4033E47d90003D41EC34EcaEda94", // ethereum / bnb / polygon
|
|
49
|
+
CoinType.SMARTCHAIN to "0x9858EfFD232B4033E47d90003D41EC34EcaEda94", // bnb (same key as ethereum)
|
|
50
|
+
CoinType.BITCOIN to "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu",
|
|
51
|
+
CoinType.LITECOIN to "ltc1qjmxnz78nmc8nq77wuxh25n2es7rzm5c2rkk4wh",
|
|
52
|
+
CoinType.XRP to "rHsMGQEkVNJmpGWs8XUBoTBiAAbwxZN5v3",
|
|
53
|
+
CoinType.TRON to "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH",
|
|
54
|
+
// Non-bounceable (UQ...) — WalletCore 4.1.19's default for this CoinType. The WASM build
|
|
55
|
+
// (@trustwallet/wallet-core 3.3.3) derives the same underlying hash but defaults to the
|
|
56
|
+
// bounceable (EQ...) encoding instead — a version/binding difference in address-flag
|
|
57
|
+
// defaults, not a key-derivation discrepancy. On-device 4.1.19 (what the app actually ships)
|
|
58
|
+
// is authoritative here.
|
|
59
|
+
CoinType.TON to "UQAzWZa6nM5mJev91wGc7VCSfBoIsYRqKJpV78N8Add9-RKY",
|
|
60
|
+
CoinType.SOLANA to "GjJyeC1r2RgkuoCWMyPYkCWSGSGLcz266EaAkLA27AhL",
|
|
61
|
+
CoinType.BITCOINCASH to "bitcoincash:qqyx49mu0kkn9ftfj6hje6g2wfer34yfnq5tahq3q6",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
// Steady state is empty — add a chain's CoinType here (and drop it from VERIFIED) only while
|
|
65
|
+
// actively harvesting a newly-added chain's address for the first time.
|
|
66
|
+
val PENDING = emptyList<CoinType>()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@Test
|
|
70
|
+
fun verifiedAddressesMatch() {
|
|
71
|
+
val wallet = HDWallet(MNEMONIC, "")
|
|
72
|
+
for ((coin, expected) in VERIFIED) {
|
|
73
|
+
val actual = wallet.getAddressForCoin(coin)
|
|
74
|
+
assertEquals("address mismatch for coin=${coin.name}", expected, actual)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Regression test for a real bug: ChainSigner.addressForChain originally derived Litecoin's
|
|
79
|
+
// and Bitcoin Cash's hand-rolled testnet address from the identical path m/44'/1'/0'/0/0.
|
|
80
|
+
// BIP32 derivation only depends on (seed, path, curve) — CoinType alone doesn't perturb it
|
|
81
|
+
// when the literal path and curve (secp256k1 for both) are the same — so LTC and BCH silently
|
|
82
|
+
// derived the *same* key, and therefore the same address. Fixed by using each coin's own
|
|
83
|
+
// SLIP-44 index as the account (3rd) path component (see ChainSigner.utxoTestnetPath).
|
|
84
|
+
@Test
|
|
85
|
+
fun testnetAddressesAreDistinctPerChain() {
|
|
86
|
+
val wallet = HDWallet(MNEMONIC, "")
|
|
87
|
+
val btc = ChainSigner.addressForChain(wallet, ChainKey.BITCOIN, isTestnet = true)
|
|
88
|
+
val ltc = ChainSigner.addressForChain(wallet, ChainKey.LITECOIN, isTestnet = true)
|
|
89
|
+
val bch = ChainSigner.addressForChain(wallet, ChainKey.BITCOINCASH, isTestnet = true)
|
|
90
|
+
println("[AddressDerivationConformanceTest] testnet: BTC=$btc LTC=$ltc BCH=$bch")
|
|
91
|
+
assertNotEquals("BTC/LTC testnet addresses must not collide", btc, ltc)
|
|
92
|
+
assertNotEquals("BTC/BCH testnet addresses must not collide", btc, bch)
|
|
93
|
+
assertNotEquals("LTC/BCH testnet addresses must not collide", ltc, bch)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@Test
|
|
97
|
+
fun printPendingAddressesForVerification() {
|
|
98
|
+
val wallet = HDWallet(MNEMONIC, "")
|
|
99
|
+
val lines = PENDING.map { coin -> " ${coin.name} -> ${wallet.getAddressForCoin(coin)}" }
|
|
100
|
+
println(
|
|
101
|
+
"\n[AddressDerivationConformanceTest] Pending — verify and move to VERIFIED map:\n" +
|
|
102
|
+
lines.joinToString("\n")
|
|
103
|
+
)
|
|
104
|
+
// This test intentionally never fails — it exists only to harvest pending addresses.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
package com.chainberry.trustwalletcore
|
|
2
|
+
|
|
3
|
+
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
4
|
+
import org.junit.Assert.assertEquals
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
import org.junit.runner.RunWith
|
|
7
|
+
import wallet.core.jni.BitcoinScript
|
|
8
|
+
import wallet.core.jni.CoinType
|
|
9
|
+
import wallet.core.jni.HDWallet
|
|
10
|
+
|
|
11
|
+
// Instrumented test — must run on a device or emulator (needs the TrustWalletCore JNI library).
|
|
12
|
+
// Run via: ./gradlew connectedDebugAndroidTest
|
|
13
|
+
//
|
|
14
|
+
// Verifies that ChainSigner.sign() — the exact same call path ChainberryTrustWalletCoreModule.kt uses in
|
|
15
|
+
// production — produces the expected byte-for-byte signed output for a fixed, representative
|
|
16
|
+
// unsignedTx per chain, under WalletCore 4.1.19 (the pinned version). Inputs are deterministic
|
|
17
|
+
// but NOT necessarily broadcast-valid (fake txids/blockhash/etc.) — this is a regression fixture,
|
|
18
|
+
// not a live-network test.
|
|
19
|
+
//
|
|
20
|
+
// BCH is intentionally absent — ChainSigner.sign() throws for BITCOINCASH (sending unsupported
|
|
21
|
+
// on both platforms; see ChainSigning.kt). Every other advertised chain has exactly one vector,
|
|
22
|
+
// except EVM: `ethereum` covers the legacy gasPriceHex path and `polygon` covers the separate
|
|
23
|
+
// EIP-1559 maxFeePerGasHex/maxPriorityFeePerGasHex branch in signEvm — bnb needs no case of its
|
|
24
|
+
// own beyond that (same code path/CoinType as ethereum).
|
|
25
|
+
//
|
|
26
|
+
// Human-readable source of truth: conformance/signing-vectors.json.
|
|
27
|
+
@RunWith(AndroidJUnit4::class)
|
|
28
|
+
class SigningConformanceTest {
|
|
29
|
+
|
|
30
|
+
companion object {
|
|
31
|
+
init { System.loadLibrary("TrustWalletCore") }
|
|
32
|
+
|
|
33
|
+
const val MNEMONIC = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
|
34
|
+
|
|
35
|
+
// Wallet's own on-device-derived addresses (see AddressDerivationConformanceTest.VERIFIED) —
|
|
36
|
+
// used as toAddress/changeAddress/Account/feePayer below so every UTXO/XRP/Solana vector is a
|
|
37
|
+
// (fake, never-broadcast) *self-send*, keeping this fixture self-contained with no external
|
|
38
|
+
// address dependency.
|
|
39
|
+
const val ETH_ADDR = "0x9858EfFD232B4033E47d90003D41EC34EcaEda94"
|
|
40
|
+
const val BTC_ADDR = "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
|
|
41
|
+
const val LTC_ADDR = "ltc1qjmxnz78nmc8nq77wuxh25n2es7rzm5c2rkk4wh"
|
|
42
|
+
const val XRP_ADDR = "rHsMGQEkVNJmpGWs8XUBoTBiAAbwxZN5v3"
|
|
43
|
+
const val TON_ADDR = "UQAzWZa6nM5mJev91wGc7VCSfBoIsYRqKJpV78N8Add9-RKY"
|
|
44
|
+
|
|
45
|
+
// Fixed, deterministic (not necessarily real/broadcastable) inputs per chain.
|
|
46
|
+
fun vectors(): Map<String, Pair<ChainKey, Map<String, Any>>> {
|
|
47
|
+
// Same 32-byte all-zero value reused below purely as a deterministic placeholder txid/hash
|
|
48
|
+
// — never broadcast, so it doesn't need to reference a real UTXO/blockhash.
|
|
49
|
+
val fakeTxId = "00".repeat(32)
|
|
50
|
+
|
|
51
|
+
return linkedMapOf(
|
|
52
|
+
"ethereum" to (ChainKey.ETHEREUM to mapOf(
|
|
53
|
+
"to" to ETH_ADDR,
|
|
54
|
+
"chainId" to 1,
|
|
55
|
+
"nonce" to 0,
|
|
56
|
+
"gasLimitHex" to "5208", // 21000
|
|
57
|
+
"valueHex" to "de0b6b3a7640000", // 1 ETH
|
|
58
|
+
"gasPriceHex" to "4a817c800", // 20 gwei — legacy path
|
|
59
|
+
)),
|
|
60
|
+
"polygon" to (ChainKey.POLYGON to mapOf(
|
|
61
|
+
"to" to ETH_ADDR,
|
|
62
|
+
"chainId" to 137,
|
|
63
|
+
"nonce" to 0,
|
|
64
|
+
"gasLimitHex" to "5208",
|
|
65
|
+
"valueHex" to "de0b6b3a7640000",
|
|
66
|
+
"maxFeePerGasHex" to "9502f9000", // EIP-1559 path
|
|
67
|
+
"maxPriorityFeePerGasHex" to "3b9aca00",
|
|
68
|
+
)),
|
|
69
|
+
"bitcoin" to (ChainKey.BITCOIN to utxoTx(CoinType.BITCOIN, BTC_ADDR, fakeTxId)),
|
|
70
|
+
"litecoin" to (ChainKey.LITECOIN to utxoTx(CoinType.LITECOIN, LTC_ADDR, fakeTxId)),
|
|
71
|
+
"xrp" to (ChainKey.XRP to mapOf(
|
|
72
|
+
"Account" to XRP_ADDR,
|
|
73
|
+
"Destination" to XRP_ADDR,
|
|
74
|
+
"Amount" to "1000000", // 1 XRP, drops
|
|
75
|
+
"Fee" to "12",
|
|
76
|
+
"Sequence" to 1,
|
|
77
|
+
"LastLedgerSequence" to 100,
|
|
78
|
+
)),
|
|
79
|
+
"ton" to (ChainKey.TON to mapOf(
|
|
80
|
+
"toAddress" to TON_ADDR,
|
|
81
|
+
"amount" to "1000000000", // 1 TON, nanotons
|
|
82
|
+
"seqno" to 1,
|
|
83
|
+
"memoId" to "conformance-test",
|
|
84
|
+
)),
|
|
85
|
+
"tron" to (ChainKey.TRON to mapOf(
|
|
86
|
+
// A genuinely all-zero digest degenerates silently in wallet-core's direct-sign path
|
|
87
|
+
// (empty signature, no error) — use a non-trivial fixed fake digest instead.
|
|
88
|
+
"txID" to "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
89
|
+
)),
|
|
90
|
+
"solana" to (ChainKey.SOLANA to mapOf(
|
|
91
|
+
// Fixed unsigned tx built via @solana/web3.js: feePayer/from = this wallet's own
|
|
92
|
+
// Solana address, to = the System Program id (32 zero bytes, a fixed valid pubkey),
|
|
93
|
+
// recentBlockhash = the same all-zero value (never broadcast, only needs to be
|
|
94
|
+
// shape-valid base58 for TransactionDecoder). See conformance/signing-vectors.json's
|
|
95
|
+
// solana entry for the exact base64 blob and how it was generated.
|
|
96
|
+
"unsignedTxBase64" to "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAC6bYGKEG7l3rSHeceyWGQBjPCbyE4TgFbAUpjemFJlUcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAEMAgAAAEBCDwAAAAAA",
|
|
97
|
+
)),
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// scriptPubKeyHex is computed here (BitcoinScript.lockScriptForAddress), not hand-derived from
|
|
102
|
+
// bech32, so the UTXO vector always matches this wallet's own real P2WPKH lock script.
|
|
103
|
+
private fun utxoTx(coin: CoinType, ownAddress: String, fakeTxId: String): Map<String, Any> {
|
|
104
|
+
val scriptHex = BitcoinScript.lockScriptForAddress(ownAddress, coin).data()
|
|
105
|
+
.joinToString("") { "%02x".format(it) }
|
|
106
|
+
return mapOf(
|
|
107
|
+
"toAddress" to ownAddress,
|
|
108
|
+
"changeAddress" to ownAddress,
|
|
109
|
+
"sendAmountSats" to "50000",
|
|
110
|
+
"changeAmountSats" to "40000",
|
|
111
|
+
"satsPerByte" to 10,
|
|
112
|
+
"inputs" to listOf(
|
|
113
|
+
mapOf(
|
|
114
|
+
"txIdHex" to fakeTxId,
|
|
115
|
+
"vout" to 0,
|
|
116
|
+
"amountSats" to "100000",
|
|
117
|
+
"scriptPubKeyHex" to scriptHex,
|
|
118
|
+
)
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Expected signed outputs, confirmed on-device against WalletCore 4.1.19 (harvested via
|
|
124
|
+
// printSigningVectorsForVerification, see conformance/signing-vectors.json for the full
|
|
125
|
+
// methodology). "ton" is deliberately absent — signTon() embeds a wall-clock
|
|
126
|
+
// `expireAt = now + 600s` into the signed payload, so its output is never byte-reproducible
|
|
127
|
+
// across runs; see tonVectorIsWellFormed() below for what's actually checked instead.
|
|
128
|
+
val EXPECTED: Map<String, String> = mapOf(
|
|
129
|
+
"ethereum" to "0xf86c808504a817c800825208949858effd232b4033e47d90003d41ec34ecaeda94880de0b6b3a76400008026a0b7cca5f69561cd482cec4e692bd2da17d9eb1d6b3a83fe3f7f11d117de99ca97a00ad0dc153460e39b355c226a2744fc2926f84b95b89c954551571f1bbc251478",
|
|
130
|
+
"polygon" to "0x02f874818980843b9aca008509502f9000825208949858effd232b4033e47d90003d41ec34ecaeda94880de0b6b3a764000080c001a038b5c72ee38aa18607462b80ab0cb170a8d4df1c4090ac49acd41d6b4b0f9acca05bdee390ee8a50ff569f55d408371213df72a3065902b07317289b39788cb434",
|
|
131
|
+
"bitcoin" to "0100000000010100000000000000000000000000000000000000000000000000000000000000000000000000000000000250c3000000000000160014c0cebcd6c3d3ca8c75dc5ec62ebe55330ef910e2cebd000000000000160014c0cebcd6c3d3ca8c75dc5ec62ebe55330ef910e20247304402201e8f3663c97712bbf662c96df532e701ca9b660b507ab170d7b4ffbd966d4be0022033bd537c7720b1e7be2b7663d1a0adcbe565ff18a25794aec828015529ab80e101210330d54fd0dd420a6e5f8d3624f5f3482cae350f79d5f0753bf5beef9c2d91af3c00000000",
|
|
132
|
+
"litecoin" to "0100000000010100000000000000000000000000000000000000000000000000000000000000000000000000000000000250c300000000000016001496cd3178f3de0f307bcee1aeaa4d5987862dd30acebd00000000000016001496cd3178f3de0f307bcee1aeaa4d5987862dd30a0248304502210093a73dec624132e9ea3d1a629c0c08be77be68416b0f99cbeb23381967ce8c560220385912e0b0835bcd1dd844bae68e4df208af8b07b095d5ac5ce6cd4407a27a85012102e49c9b9b5d0f127235dc26a0c252814c52fb333d651a946773f59d72c2da990400000000",
|
|
133
|
+
"xrp" to "12000022000000002400000001201b000000646140000000000f424068400000000000000c7321031d68bc1a142e6766b2bdfb006ccfe135ef2e0e2e94abb5cf5c9ab6104776fbae7446304402207fb80c118799a9b3d4dad6e91ad1b8f0c5c9d189a0bcdaf50e823ea44f8044e70220422f638bf4b4e634cb39a4250cf72513d5e3c7e810a13c2f35b5df54b02dc7c98114aff3c2e33458b30714ca16ffee19952dd35c17c88314aff3c2e33458b30714ca16ffee19952dd35c17c8",
|
|
134
|
+
"tron" to "{\"txID\":\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"signature\":[\"4b63d125c56637e9f09d9578bf9fbf30e0e6796085bb0fe8aa7c466b2ae9bc690a26d26fe36ff0d42d416ce76b70b486c50c0294b50d5a6e9871d2f3a7ebd28901\"]}",
|
|
135
|
+
"solana" to "Aa+eJgDHJo2f9vGrz1EpPPnfGWcZoySFIEw0sS7OVvYiF4U421YCQqLme/uESu3SLan0AM6xblAfUgYlM28StQcBAAAC6bYGKEG7l3rSHeceyWGQBjPCbyE4TgFbAUpjemFJlUcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAEMAgAAAEBCDwAAAAAA",
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@Test
|
|
140
|
+
fun printSigningVectorsForVerification() {
|
|
141
|
+
val wallet = HDWallet(MNEMONIC, "")
|
|
142
|
+
val lines = vectors().map { (chain, pair) ->
|
|
143
|
+
val (chainKey, unsignedTx) = pair
|
|
144
|
+
val result = try {
|
|
145
|
+
ChainSigner.sign(chainKey, wallet, unsignedTx, isTestnet = false)
|
|
146
|
+
} catch (e: Exception) {
|
|
147
|
+
return@map " $chain -> ERROR: ${e.message}"
|
|
148
|
+
}
|
|
149
|
+
val metaStr = result.meta?.let { " meta=$it" } ?: ""
|
|
150
|
+
" $chain -> ${result.signedTx}$metaStr"
|
|
151
|
+
}
|
|
152
|
+
println(
|
|
153
|
+
"\n[SigningConformanceTest] Harvested signed outputs — verify and populate EXPECTED:\n" +
|
|
154
|
+
lines.joinToString("\n")
|
|
155
|
+
)
|
|
156
|
+
// This test intentionally never fails — it exists only to harvest signing vectors.
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@Test
|
|
160
|
+
fun signedOutputsMatch() {
|
|
161
|
+
val wallet = HDWallet(MNEMONIC, "")
|
|
162
|
+
for ((chain, pair) in vectors()) {
|
|
163
|
+
val expected = EXPECTED[chain] ?: continue
|
|
164
|
+
val (chainKey, unsignedTx) = pair
|
|
165
|
+
val actual = ChainSigner.sign(chainKey, wallet, unsignedTx, isTestnet = false).signedTx
|
|
166
|
+
assertEquals("signed output mismatch for chain=$chain", expected, actual)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// TON's signTon() embeds a wall-clock `expireAt = now + 600s` into the signed BOC, so its
|
|
171
|
+
// output (and meta.txHash, which hashes that BOC) is never byte-reproducible across runs —
|
|
172
|
+
// a real constraint of the production code, not a test gap. This checks the parts that ARE
|
|
173
|
+
// deterministic instead: signing succeeds, the output is a well-formed base64 BOC, and
|
|
174
|
+
// meta.txHash is present and the right shape.
|
|
175
|
+
@Test
|
|
176
|
+
fun tonVectorIsWellFormed() {
|
|
177
|
+
val wallet = HDWallet(MNEMONIC, "")
|
|
178
|
+
val (chainKey, unsignedTx) = vectors().getValue("ton")
|
|
179
|
+
val result = ChainSigner.sign(chainKey, wallet, unsignedTx, isTestnet = false)
|
|
180
|
+
assert(result.signedTx.startsWith("te6cc")) {
|
|
181
|
+
"TON signed output doesn't look like a BOC (expected te6cc... prefix): ${result.signedTx}"
|
|
182
|
+
}
|
|
183
|
+
val txHash = result.meta?.get("txHash") as? String
|
|
184
|
+
assertEquals("TON meta.txHash should be a 32-byte hex string", 64, txHash?.length)
|
|
185
|
+
}
|
|
186
|
+
}
|