ox 0.10.2 → 0.10.4
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/CHANGELOG.md +14 -0
- package/_cjs/tempo/SignatureEnvelope.js +66 -7
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/SignatureEnvelope.js +169 -6
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +132 -3
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/package.json +2 -2
- package/tempo/SignatureEnvelope.test.ts +554 -1
- package/tempo/SignatureEnvelope.ts +205 -3
- package/version.ts +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Address from '../core/Address.js'
|
|
2
|
+
import type * as Bytes from '../core/Bytes.js'
|
|
2
3
|
import * as Errors from '../core/Errors.js'
|
|
3
4
|
import * as Hex from '../core/Hex.js'
|
|
4
5
|
import type {
|
|
@@ -10,15 +11,22 @@ import type {
|
|
|
10
11
|
UnionPartialBy,
|
|
11
12
|
} from '../core/internal/types.js'
|
|
12
13
|
import * as Json from '../core/Json.js'
|
|
14
|
+
import * as ox_P256 from '../core/P256.js'
|
|
13
15
|
import type * as PublicKey from '../core/PublicKey.js'
|
|
16
|
+
import * as ox_Secp256k1 from '../core/Secp256k1.js'
|
|
14
17
|
import * as Signature from '../core/Signature.js'
|
|
15
18
|
import type * as WebAuthnP256 from '../core/WebAuthnP256.js'
|
|
19
|
+
import * as ox_WebAuthnP256 from '../core/WebAuthnP256.js'
|
|
16
20
|
|
|
17
21
|
/** Signature type identifiers for encoding/decoding */
|
|
18
22
|
const serializedP256Type = '0x01'
|
|
19
23
|
const serializedWebAuthnType = '0x02'
|
|
20
24
|
const serializedKeychainType = '0x03'
|
|
21
25
|
|
|
26
|
+
/** Serialized magic identifier for Tempo signature envelopes. */
|
|
27
|
+
export const magicBytes =
|
|
28
|
+
'0x7777777777777777777777777777777777777777777777777777777777777777' // 32 "T"s
|
|
29
|
+
|
|
22
30
|
/**
|
|
23
31
|
* Statically determines the signature type of an envelope at compile time.
|
|
24
32
|
*
|
|
@@ -283,7 +291,11 @@ export declare namespace assert {
|
|
|
283
291
|
* @returns The deserialized signature envelope.
|
|
284
292
|
* @throws `CoercionError` if the serialized value cannot be coerced to a valid signature envelope.
|
|
285
293
|
*/
|
|
286
|
-
export function deserialize(
|
|
294
|
+
export function deserialize(value: Serialized): SignatureEnvelope {
|
|
295
|
+
const serialized = value.endsWith(magicBytes.slice(2))
|
|
296
|
+
? Hex.slice(value, 0, -Hex.size(magicBytes))
|
|
297
|
+
: value
|
|
298
|
+
|
|
287
299
|
const size = Hex.size(serialized)
|
|
288
300
|
|
|
289
301
|
// Backward compatibility: 65 bytes means secp256k1 without type identifier
|
|
@@ -763,13 +775,17 @@ export function getType<
|
|
|
763
775
|
*/
|
|
764
776
|
export function serialize(
|
|
765
777
|
envelope: UnionPartialBy<SignatureEnvelope, 'prehash'>,
|
|
778
|
+
options: serialize.Options = {},
|
|
766
779
|
): Serialized {
|
|
767
780
|
const type = getType(envelope)
|
|
768
781
|
|
|
769
782
|
// Backward compatibility: no type identifier for secp256k1
|
|
770
783
|
if (type === 'secp256k1') {
|
|
771
784
|
const secp256k1 = envelope as Secp256k1
|
|
772
|
-
return
|
|
785
|
+
return Hex.concat(
|
|
786
|
+
Signature.toHex(secp256k1.signature),
|
|
787
|
+
options.magic ? magicBytes : '0x',
|
|
788
|
+
)
|
|
773
789
|
}
|
|
774
790
|
|
|
775
791
|
if (type === 'p256') {
|
|
@@ -782,6 +798,7 @@ export function serialize(
|
|
|
782
798
|
Hex.fromNumber(p256.publicKey.x, { size: 32 }),
|
|
783
799
|
Hex.fromNumber(p256.publicKey.y, { size: 32 }),
|
|
784
800
|
Hex.fromNumber(p256.prehash ? 1 : 0, { size: 1 }),
|
|
801
|
+
options.magic ? magicBytes : '0x',
|
|
785
802
|
)
|
|
786
803
|
}
|
|
787
804
|
|
|
@@ -800,6 +817,7 @@ export function serialize(
|
|
|
800
817
|
Hex.fromNumber(webauthn.signature.s, { size: 32 }),
|
|
801
818
|
Hex.fromNumber(webauthn.publicKey.x, { size: 32 }),
|
|
802
819
|
Hex.fromNumber(webauthn.publicKey.y, { size: 32 }),
|
|
820
|
+
options.magic ? magicBytes : '0x',
|
|
803
821
|
)
|
|
804
822
|
}
|
|
805
823
|
|
|
@@ -809,12 +827,23 @@ export function serialize(
|
|
|
809
827
|
serializedKeychainType,
|
|
810
828
|
keychain.userAddress,
|
|
811
829
|
serialize(keychain.inner),
|
|
830
|
+
options.magic ? magicBytes : '0x',
|
|
812
831
|
)
|
|
813
832
|
}
|
|
814
833
|
|
|
815
834
|
throw new CoercionError({ envelope })
|
|
816
835
|
}
|
|
817
836
|
|
|
837
|
+
export declare namespace serialize {
|
|
838
|
+
type Options = {
|
|
839
|
+
/**
|
|
840
|
+
* Whether to serialize the signature envelope with the Tempo magic identifier.
|
|
841
|
+
* This is useful for being able to distinguish between Tempo and non-Tempo (e.g. ERC-1271) signatures.
|
|
842
|
+
*/
|
|
843
|
+
magic?: boolean | undefined
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
818
847
|
/**
|
|
819
848
|
* Converts a signature envelope to RPC format.
|
|
820
849
|
*
|
|
@@ -922,6 +951,179 @@ export declare namespace validate {
|
|
|
922
951
|
type ErrorType = Errors.GlobalErrorType
|
|
923
952
|
}
|
|
924
953
|
|
|
954
|
+
/**
|
|
955
|
+
* Verifies a signature envelope against a digest/payload.
|
|
956
|
+
*
|
|
957
|
+
* Supports `secp256k1`, `p256`, and `webAuthn` signature types.
|
|
958
|
+
*
|
|
959
|
+
* :::warning
|
|
960
|
+
* `keychain` signatures are not supported and will return `false`.
|
|
961
|
+
* :::
|
|
962
|
+
*
|
|
963
|
+
* @example
|
|
964
|
+
* ### Secp256k1
|
|
965
|
+
*
|
|
966
|
+
* ```ts twoslash
|
|
967
|
+
* import { SignatureEnvelope } from 'ox/tempo'
|
|
968
|
+
* import { Secp256k1 } from 'ox'
|
|
969
|
+
*
|
|
970
|
+
* const privateKey = Secp256k1.randomPrivateKey()
|
|
971
|
+
* const publicKey = Secp256k1.getPublicKey({ privateKey })
|
|
972
|
+
* const payload = '0xdeadbeef'
|
|
973
|
+
*
|
|
974
|
+
* const signature = Secp256k1.sign({ payload, privateKey })
|
|
975
|
+
* const envelope = SignatureEnvelope.from(signature)
|
|
976
|
+
*
|
|
977
|
+
* const valid = SignatureEnvelope.verify(envelope, {
|
|
978
|
+
* payload,
|
|
979
|
+
* publicKey,
|
|
980
|
+
* })
|
|
981
|
+
* // @log: true
|
|
982
|
+
* ```
|
|
983
|
+
*
|
|
984
|
+
* @example
|
|
985
|
+
* ### P256
|
|
986
|
+
*
|
|
987
|
+
* For P256 signatures, the `address` or `publicKey` must match the embedded
|
|
988
|
+
* public key in the signature envelope.
|
|
989
|
+
*
|
|
990
|
+
* ```ts twoslash
|
|
991
|
+
* import { SignatureEnvelope } from 'ox/tempo'
|
|
992
|
+
* import { P256 } from 'ox'
|
|
993
|
+
*
|
|
994
|
+
* const privateKey = P256.randomPrivateKey()
|
|
995
|
+
* const publicKey = P256.getPublicKey({ privateKey })
|
|
996
|
+
* const payload = '0xdeadbeef'
|
|
997
|
+
*
|
|
998
|
+
* const signature = P256.sign({ payload, privateKey })
|
|
999
|
+
* const envelope = SignatureEnvelope.from({ prehash: false, publicKey, signature })
|
|
1000
|
+
*
|
|
1001
|
+
* const valid = SignatureEnvelope.verify(envelope, {
|
|
1002
|
+
* payload,
|
|
1003
|
+
* publicKey,
|
|
1004
|
+
* })
|
|
1005
|
+
* // @log: true
|
|
1006
|
+
* ```
|
|
1007
|
+
*
|
|
1008
|
+
* @example
|
|
1009
|
+
* ### WebCryptoP256
|
|
1010
|
+
*
|
|
1011
|
+
* ```ts twoslash
|
|
1012
|
+
* import { SignatureEnvelope } from 'ox/tempo'
|
|
1013
|
+
* import { WebCryptoP256 } from 'ox'
|
|
1014
|
+
*
|
|
1015
|
+
* const { privateKey, publicKey } = await WebCryptoP256.createKeyPair()
|
|
1016
|
+
* const payload = '0xdeadbeef'
|
|
1017
|
+
*
|
|
1018
|
+
* const signature = await WebCryptoP256.sign({ payload, privateKey })
|
|
1019
|
+
* const envelope = SignatureEnvelope.from({ prehash: true, publicKey, signature })
|
|
1020
|
+
*
|
|
1021
|
+
* const valid = SignatureEnvelope.verify(envelope, {
|
|
1022
|
+
* payload,
|
|
1023
|
+
* publicKey,
|
|
1024
|
+
* })
|
|
1025
|
+
* // @log: true
|
|
1026
|
+
* ```
|
|
1027
|
+
*
|
|
1028
|
+
* @example
|
|
1029
|
+
* ### WebAuthnP256
|
|
1030
|
+
*
|
|
1031
|
+
* ```ts twoslash
|
|
1032
|
+
* import { SignatureEnvelope } from 'ox/tempo'
|
|
1033
|
+
* import { WebAuthnP256 } from 'ox'
|
|
1034
|
+
*
|
|
1035
|
+
* const credential = await WebAuthnP256.createCredential({ name: 'Example' })
|
|
1036
|
+
* const payload = '0xdeadbeef'
|
|
1037
|
+
*
|
|
1038
|
+
* const { metadata, signature } = await WebAuthnP256.sign({
|
|
1039
|
+
* challenge: payload,
|
|
1040
|
+
* credentialId: credential.id,
|
|
1041
|
+
* })
|
|
1042
|
+
* const envelope = SignatureEnvelope.from({
|
|
1043
|
+
* metadata,
|
|
1044
|
+
* signature,
|
|
1045
|
+
* publicKey: credential.publicKey,
|
|
1046
|
+
* })
|
|
1047
|
+
*
|
|
1048
|
+
* const valid = SignatureEnvelope.verify(envelope, {
|
|
1049
|
+
* payload,
|
|
1050
|
+
* publicKey: credential.publicKey,
|
|
1051
|
+
* })
|
|
1052
|
+
* // @log: true
|
|
1053
|
+
* ```
|
|
1054
|
+
*
|
|
1055
|
+
* @param parameters - Verification parameters.
|
|
1056
|
+
* @returns `true` if the signature is valid, `false` otherwise.
|
|
1057
|
+
*/
|
|
1058
|
+
export function verify(
|
|
1059
|
+
signature: SignatureEnvelope,
|
|
1060
|
+
parameters: verify.Parameters,
|
|
1061
|
+
): boolean {
|
|
1062
|
+
const { payload } = parameters
|
|
1063
|
+
|
|
1064
|
+
const address = (() => {
|
|
1065
|
+
if (parameters.address) return parameters.address
|
|
1066
|
+
if (parameters.publicKey) return Address.fromPublicKey(parameters.publicKey)
|
|
1067
|
+
return undefined
|
|
1068
|
+
})()
|
|
1069
|
+
if (!address) return false
|
|
1070
|
+
|
|
1071
|
+
try {
|
|
1072
|
+
const envelope = from(signature)
|
|
1073
|
+
|
|
1074
|
+
if (envelope.type === 'secp256k1') {
|
|
1075
|
+
if (!address) return false
|
|
1076
|
+
return ox_Secp256k1.verify({
|
|
1077
|
+
address,
|
|
1078
|
+
payload,
|
|
1079
|
+
signature: envelope.signature,
|
|
1080
|
+
})
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
if (envelope.type === 'p256') {
|
|
1084
|
+
const envelopeAddress = Address.fromPublicKey(envelope.publicKey)
|
|
1085
|
+
if (!Address.isEqual(envelopeAddress, address)) return false
|
|
1086
|
+
return ox_P256.verify({
|
|
1087
|
+
hash: envelope.prehash,
|
|
1088
|
+
publicKey: envelope.publicKey,
|
|
1089
|
+
payload,
|
|
1090
|
+
signature: envelope.signature,
|
|
1091
|
+
})
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
if (envelope.type === 'webAuthn') {
|
|
1095
|
+
const envelopeAddress = Address.fromPublicKey(envelope.publicKey)
|
|
1096
|
+
if (!Address.isEqual(envelopeAddress, address)) return false
|
|
1097
|
+
return ox_WebAuthnP256.verify({
|
|
1098
|
+
challenge: Hex.from(payload),
|
|
1099
|
+
metadata: envelope.metadata,
|
|
1100
|
+
publicKey: envelope.publicKey,
|
|
1101
|
+
signature: envelope.signature,
|
|
1102
|
+
})
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
return false
|
|
1106
|
+
} catch {
|
|
1107
|
+
return false
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
export declare namespace verify {
|
|
1112
|
+
type Parameters = {
|
|
1113
|
+
/** Payload that was signed. */
|
|
1114
|
+
payload: Hex.Hex | Bytes.Bytes
|
|
1115
|
+
} & OneOf<
|
|
1116
|
+
| {
|
|
1117
|
+
/** Public key that signed the payload. */
|
|
1118
|
+
publicKey: PublicKey.PublicKey
|
|
1119
|
+
}
|
|
1120
|
+
| {
|
|
1121
|
+
/** Address that signed the payload. */
|
|
1122
|
+
address: Address.Address
|
|
1123
|
+
}
|
|
1124
|
+
>
|
|
1125
|
+
}
|
|
1126
|
+
|
|
925
1127
|
/**
|
|
926
1128
|
* Error thrown when a signature envelope cannot be coerced to a valid type.
|
|
927
1129
|
*/
|
package/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @internal */
|
|
2
|
-
export const version = '0.10.
|
|
2
|
+
export const version = '0.10.4'
|