All Downloads are FREE. Search and download functionalities are using the official Maven repository.

convex.core.data.SignedData Maven / Gradle / Ivy

The newest version!
package convex.core.data;

import convex.core.crypto.AKeyPair;
import convex.core.crypto.ASignature;
import convex.core.crypto.Ed25519Signature;
import convex.core.crypto.Providers;
import convex.core.exceptions.BadFormatException;
import convex.core.exceptions.BadSignatureException;
import convex.core.exceptions.InvalidDataException;
import convex.core.lang.RecordFormat;

/**
 * Node representing a signed data object.
 *
 * A signed data object encapsulates:
 * 
    *
  • An Address that identifies the signer
  • *
  • A digital signature
  • *
  • An underlying Cell that has been signed.
  • *
* * The SignedData instance is considered valid if the signature can be successfully validated for * the given Address and data value, and if so can be taken as a cryptographic proof that the signature * was created by someone in possession of the corresponding private key. * * Note we currently go via a Ref here for a few reasons: - It guarantees we * have a hash for signing - It makes the SignedData object * implementation/representation independent of the value type - It creates a * possibility of structural sharing for transaction values excluding signatures * * Binary representation: *
    *
  1. 1 byte - Tag.SIGNED_DATA tag
  2. *
  3. 32 bytes - Public Key of signer
  4. *
  5. 64 bytes - raw Signature data
  6. *
  7. 1+ bytes - Data Value Ref (may be embedded)
  8. *
* * SECURITY: signing requires presence of a local keypair TODO: SECURITY: any * persistence forces validation of Signature?? * * @param The type of the signed object */ public final class SignedData extends ARecord { // Encoded fields private final AccountKey pubKey; private final ASignature signature; private final Ref valueRef; private static final Keyword[] KEYS = new Keyword[] { Keywords.PUBLIC_KEY, Keywords.SIGNATURE, Keywords.VALUE }; private static final RecordFormat FORMAT = RecordFormat.of(KEYS); //Cached fields private AccountKey verifiedKey=null; private SignedData(Ref refToValue, AccountKey address, ASignature sig) { super(FORMAT.count()); this.valueRef = refToValue; this.pubKey = address; signature = sig; } /** * Signs a data value Ref with the given keypair. * * SECURITY: Marks as already validated, since we just signed it. * * @param keyPair The public/private key pair of the signer. * @param ref Ref to the data to sign * @return SignedData object signed with the given key-pair */ public static SignedData signRef(AKeyPair keyPair, Ref ref) { Blob message=getMessageForRef(ref); ASignature sig = keyPair.sign(message); SignedData sd = new SignedData(ref, keyPair.getAccountKey(), sig); sd.markValidated(); return sd; } public static Blob getMessageForRef(Ref ref) { return ref.getEncoding(); } /** * Mark this SignedData as already verified as good - cache in Ref */ private void markValidated() { Ref ref=getRef(); int flags=ref.getFlags(); if ((flags&Ref.VERIFIED_MASK)!=0) return; // already done ref.setFlags(flags|Ref.VERIFIED_MASK); } /** * Mark this SignedData as a bad signature - cache in Ref */ private void markBadSignature() { Ref ref=getRef(); int flags=ref.getFlags(); if ((flags&Ref.BAD_MASK)!=0) return; // already done ref.setFlags(flags|Ref.BAD_MASK); } /** * Create a SignedData by signing a value with the given key pair * @param Type of value to sign * @param keyPair Key pair to sign with * @param value Any cell value to sign * @return A new SignedData instance */ public static SignedData sign(AKeyPair keyPair, T value) { return signRef(keyPair, Ref.get(value)); } /** * Creates a SignedData object with the given parameters. The resulting SignedData will be a short version without the public key. * * @param Type of value to sign * @param sig Signature of the supplied data * @param ref Ref to the data that has been signed * @return A new SignedData instance */ public static SignedData create(ASignature sig, Ref ref) { return create(null,sig,ref); } /** * Creates a SignedData object with the given parameters. * * SECURITY: Not assumed to be valid. * * @param Type of value to sign * @param address Public key of the signer * @param sig Signature of the supplied data * @param ref Ref to the data that has been signed * @return A new SignedData instance */ public static SignedData create(AccountKey address, ASignature sig, Ref ref) { return new SignedData(ref, address, sig); } /** * Gets the signed value object encapsulated by this SignedData object. * * Does not check Signature. * * @return Data value that has been signed */ public T getValue() { return valueRef.getValue(); } /** * Gets the public key of the signer. If the signature is valid, this * represents a cryptographic proof that the signer was in possession of the * private key of this address. * * @return Public Key of signer. */ public AccountKey getAccountKey() { return pubKey; } /** * Gets the Signature that formed part of this SignedData object * * @return Signature instance */ public ASignature getSignature() { return signature; } @Override public ACell get(Keyword key) { if (Keywords.PUBLIC_KEY.equals(key)) return pubKey; if (Keywords.SIGNATURE.equals(key)) return signature; if (Keywords.VALUE.equals(key)) return getValue(); return null; } @Override public int encode(byte[] bs, int pos) { byte tag=Tag.SIGNED_DATA; bs[pos++]=tag; return encodeRaw(bs,pos); } @Override public int encodeRaw(byte[] bs, int pos) { pos = pubKey.getBytes(bs,pos); pos = signature.getBytes(bs, pos); pos = valueRef.encode(bs,pos); return pos; } @Override public int estimatedEncodingSize() { return 1+AccountKey.LENGTH+Ed25519Signature.SIGNATURE_LENGTH+Format.MAX_EMBEDDED_LENGTH; } /** * Reads a SignedData instance from the given Blob encoding * * @param b Blob to read from * @param pos Start position in Blob (location of tag byte) * @return New decoded instance * @throws BadFormatException In the event of any encoding error */ public static SignedData read(Blob b, int pos, boolean includeKey) throws BadFormatException { int epos=pos+1; // skip tag AccountKey pubKey; if (includeKey) { pubKey=AccountKey.readRaw(b,epos); epos+=AccountKey.LENGTH; } else { pubKey=null; } ASignature sig = Ed25519Signature.readRaw(b,epos); epos+=Ed25519Signature.SIGNATURE_LENGTH; Ref value=Format.readRef(b, epos); epos+=value.getEncodingLength(); SignedData result=create(pubKey, sig, value); Blob enc=b.slice(pos, epos); result.attachEncoding(enc); return result; } /** * Validates the signature in this SignedData instance. Caches result * * @return true if valid, false otherwise */ public boolean checkSignature() { if (pubKey==null) return false; return checkSignatureImpl(pubKey); } /** * Validates the signature in this SignedData instance. Caches result * * @param publicKey Public key to check against this signature * @return true if valid, false otherwise */ public boolean checkSignature(AccountKey publicKey) { if ((this.pubKey!=null)&&!(this.pubKey.equals(publicKey))) return false; return checkSignatureImpl(publicKey); } /** * Validates the signature in this SignedData instance. Caches result * * @return true if valid, false otherwise */ private synchronized boolean checkSignatureImpl(AccountKey publicKey) { // Fast check for already verified key if (verifiedKey!=null) return verifiedKey.equals(publicKey); Ref> sigRef=getRef(); int flags=sigRef.getFlags(); if ((flags&Ref.BAD_MASK)!=0) return false; if ((flags&Ref.VERIFIED_MASK)!=0) return true; Blob message=getMessage(); boolean check = Providers.verify(signature,message, publicKey); if (check) { markValidated(); verifiedKey=publicKey; } else { markBadSignature(); } return check; } /** * Gets the message bytes (as signed in this SignedData) * @return */ private Blob getMessage() { Blob b=getEncoding(); int offset=1+((pubKey==null)?64:96); return b.slice(offset); } /** * Checks if the signature has already gone through verification. MAy or may * not be a valid signature. * * @return true if valid, false otherwise */ public boolean isSignatureChecked() { Ref> sigRef=getRef(); int flags=sigRef.getFlags(); return (flags&(Ref.BAD_MASK|Ref.VERIFIED_MASK))!=0; } public void validateSignature() throws BadSignatureException { if (!checkSignature()) throw new BadSignatureException("Signature not valid!", this); } @Override public boolean isCanonical() { return true; } @Override public final boolean isCVMValue() { return false; } @Override public final int getRefCount() { // Value Ref only return 1; } @SuppressWarnings("unchecked") @Override public Ref getRef(int i) { if (i != 0) throw new IndexOutOfBoundsException("Illegal SignedData ref index: " + i); return (Ref) valueRef; } @Override public SignedData updateRefs(IRefFunction func) { @SuppressWarnings("unchecked") Ref newValueRef = (Ref) func.apply(valueRef); if (valueRef == newValueRef) return this; // SECURITY: preserve verification flags SignedData newSD= new SignedData(newValueRef, pubKey, signature); Ref sdr=newSD.getRef(); sdr.setFlags(Ref.mergeFlags(sdr.getFlags(), getRef().getFlags())); newSD.attachEncoding(encoding); // optimisation to keep encoding return newSD; } @Override public void validate() throws InvalidDataException { super.validate(); } @Override public void validateCell() throws InvalidDataException { if (pubKey!=null) pubKey.validate(); signature.validate(); valueRef.validate(); } public Ref getValueRef() { return valueRef; } /** * SignedData is not embedded. * main reason: We always want to persist in store to cache verification status * * @return Always false */ public boolean isEmbedded() { return false; } @Override public byte getTag() { return Tag.SIGNED_DATA; } @Override public RecordFormat getFormat() { return FORMAT; } @Override public boolean equals(ACell o) { if (!(o instanceof SignedData)) return false; @SuppressWarnings("unchecked") SignedData b=(SignedData) o; if (!signature.equals(b.signature)) return false; if (!Cells.equals(pubKey,b.pubKey)) return false; return valueRef.equals(b.valueRef); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy